diff --git a/TPIP.md b/TPIP.md index d9356243..bfea3229 100644 --- a/TPIP.md +++ b/TPIP.md @@ -1,9 +1,10 @@ # TPIP Report for vscode-cmsis-debugger -Report prepared at: 13/01/2026, 08:36:58 +Report prepared at: 20/01/2026, 15:39:57 | *Package* | *Version* | *Repository* | *License* | |---|---|---|---| | arm-none-eabi-gdb | 14.3.1 | https://artifacts.tools.arm.com/arm-none-eabi-gdb/14.3.1/ | https://developer.arm.com/GetEula?Id=15d9660a-2059-4985-85e9-c01cdd4b1ba0 | | pyocd | 0.42.0 | https://github.com/pyocd/pyOCD | https://github.com/pyocd/pyOCD/blob/v0.42.0/LICENSE | +| xml2js | 0.6.2 | https://github.com/Leonidas-from-XIV/node-xml2js | https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/LICENSE | | yaml | 2.8.2 | https://github.com/eemeli/yaml | https://github.com/eemeli/yaml/blob/main/LICENSE | diff --git a/docs/third-party-licenses.json b/docs/third-party-licenses.json index 29507f85..88a3a43b 100644 --- a/docs/third-party-licenses.json +++ b/docs/third-party-licenses.json @@ -13,6 +13,13 @@ "url": "https://github.com/pyocd/pyOCD", "license": "https://github.com/pyocd/pyOCD/blob/v0.42.0/LICENSE" }, + { + "name": "xml2js", + "version": "0.6.2", + "spdx": "MIT", + "url": "https://github.com/Leonidas-from-XIV/node-xml2js", + "license": "https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/LICENSE" + }, { "name": "yaml", "version": "2.8.2", diff --git a/package.json b/package.json index 3b79ea03..59cc4baf 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,11 @@ "id": "cmsis-debugger.liveWatch", "name": "Live Watch", "icon": "media/trace-and-live-light.svg" + }, + { + "id": "cmsis-debugger.componentViewer", + "name": "Component Viewer", + "icon": "media/trace-and-live-light.svg" } ] }, @@ -362,7 +367,8 @@ "baseContentUrl": "https://github.com/Open-CMSIS-Pack/vscode-cmsis-debugger/blob/main/README.md" }, "dependencies": { - "yaml": "^2.8.2" + "yaml": "^2.8.2", + "xml2js": "^0.6.2" }, "devDependencies": { "@open-cmsis-pack/vsce-helper": "^0.2.2", @@ -373,6 +379,7 @@ "@types/node": "^20.19.25", "@types/node-fetch": "^2.6.13", "@types/vscode": "^1.106.1", + "@types/xml2js": "^0.4.14", "@types/yargs": "^17.0.35", "@types/yarnpkg__lockfile": "^1.1.9", "@typescript-eslint/eslint-plugin": "8.53.0", diff --git a/src/cbuild-run/cbuild-run-reader.ts b/src/cbuild-run/cbuild-run-reader.ts index e8c269d9..cfc4e031 100644 --- a/src/cbuild-run/cbuild-run-reader.ts +++ b/src/cbuild-run/cbuild-run-reader.ts @@ -44,23 +44,33 @@ export class CbuildRunReader { } public getSvdFilePaths(cmsisPackRoot?: string, pname?: string): string[] { + const svdFilePaths = this.getFilePathsByType('svd', cmsisPackRoot, pname); + return svdFilePaths; + } + + public getScvdFilePaths(cmsisPackRoot?: string, pname?: string): string[] { + const scvdFilePaths = this.getFilePathsByType('scvd', cmsisPackRoot, pname); + return scvdFilePaths; + } + + private getFilePathsByType(type: 'svd' | 'scvd', cmsisPackRoot?: string, pname?: string): string[] { if (!this.cbuildRun) { return []; } - // Get SVD file descriptors + // Get file descriptors const systemDescriptions = this.cbuildRun['system-descriptions']; - const svdFileDescriptors = systemDescriptions?.filter(descriptor => descriptor.type === 'svd') ?? []; - if (svdFileDescriptors.length === 0) { + const fileDescriptors = systemDescriptions?.filter(descriptor => descriptor.type === type) ?? []; + if (fileDescriptors.length === 0) { return []; } // Replace potential ${CMSIS_PACK_ROOT} placeholder const effectiveCmsisPackRoot = cmsisPackRoot ?? getCmsisPackRootPath(); // Map to copies, leave originals untouched - const filteredSvdDescriptors = pname ? svdFileDescriptors.filter(descriptor => descriptor.pname === pname): svdFileDescriptors; - const svdFilePaths = filteredSvdDescriptors.map(descriptor => `${effectiveCmsisPackRoot + const filteredDescriptors = pname ? fileDescriptors.filter(descriptor => descriptor.pname === pname): fileDescriptors; + const filePaths = filteredDescriptors.map(descriptor => `${effectiveCmsisPackRoot ? descriptor.file.replaceAll(CMSIS_PACK_ROOT_ENVVAR, effectiveCmsisPackRoot) : descriptor.file}`); - return svdFilePaths; + return filePaths; } public getPnames(): string[] { diff --git a/src/debug-session/gdbtarget-debug-session.ts b/src/debug-session/gdbtarget-debug-session.ts index 585042b3..93ba2f72 100644 --- a/src/debug-session/gdbtarget-debug-session.ts +++ b/src/debug-session/gdbtarget-debug-session.ts @@ -106,7 +106,7 @@ export class GDBTargetDebugSession { return this.session.configuration.request === 'launch' && this.capabilities?.supportsTerminateRequest === true; } - /** Function returns string only in case of failure */ + // Function returns string only in case of failure public async evaluateGlobalExpression(expression: string, context = 'hover'): Promise { try { const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; diff --git a/src/desktop/extension.ts b/src/desktop/extension.ts index ea6835f5..836ffbdc 100644 --- a/src/desktop/extension.ts +++ b/src/desktop/extension.ts @@ -1,5 +1,5 @@ /** - * Copyright 2025 Arm Limited + * Copyright 2025-2026 Arm Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import { CpuStates } from '../features/cpu-states/cpu-states'; import { CpuStatesCommands } from '../features/cpu-states/cpu-states-commands'; import { LiveWatchTreeDataProvider } from '../views/live-watch/live-watch'; import { GenericCommands } from '../features/generic-commands'; +import { ComponentViewer } from '../views/component-viewer/component-viewer-main'; const BUILTIN_TOOLS_PATHS = [ 'tools/pyocd/pyocd', @@ -39,6 +40,7 @@ export const activate = async (context: vscode.ExtensionContext): Promise const cpuStates = new CpuStates(); const cpuStatesCommands = new CpuStatesCommands(); const cpuStatesStatusBarItem = new CpuStatesStatusBarItem(); + const componentViewer = new ComponentViewer(context); // Register the Tree View under the id from package.json liveWatchTreeDataProvider = new LiveWatchTreeDataProvider(context); @@ -54,6 +56,8 @@ export const activate = async (context: vscode.ExtensionContext): Promise cpuStatesStatusBarItem.activate(context, cpuStates); // Live Watch view liveWatchTreeDataProvider.activate(gdbtargetDebugTracker); + // Component Viewer + componentViewer.activate(gdbtargetDebugTracker); logger.debug('Extension Pack activated'); }; diff --git a/src/manifest.ts b/src/manifest.ts index dac2d854..492b03b5 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -18,3 +18,5 @@ export const PUBLISHER_NAME = 'arm'; export const EXTENSION_NAME = 'vscode-cmsis-debugger'; export const EXTENSION_ID = `${PUBLISHER_NAME}.${EXTENSION_NAME}`; export const DISPLAY_NAME = 'Arm CMSIS Debugger'; + +export const COMPONENT_VIEWER_DISPLAY_NAME = 'Arm CMSIS Component Viewer'; diff --git a/src/views/component-viewer/component-viewer-instance.ts b/src/views/component-viewer/component-viewer-instance.ts new file mode 100755 index 00000000..fed5e2de --- /dev/null +++ b/src/views/component-viewer/component-viewer-instance.ts @@ -0,0 +1,199 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import { URI } from 'vscode-uri'; +import { parseStringPromise, ParserOptions } from 'xml2js'; +import { Json } from './model/scvd-base'; +import { Resolver } from './resolver'; +import { ScvdComponentViewer } from './model/scvd-component-viewer'; +import { ScvdBase } from './model/scvd-base'; +import { StatementEngine } from './statement-engine/statement-engine'; +import { ScvdEvalContext } from './scvd-eval-context'; +import { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../debug-session'; +import { ScvdGuiTree } from './scvd-gui-tree'; + + +const xmlOpts: ParserOptions = { + explicitArray: false, + mergeAttrs: true, + explicitRoot: true, + trim: true, + // Add child objects that carry their original tag name via '#name' + explicitChildren: true, + preserveChildrenOrder: true +}; + +export class ComponentViewerInstance { + private _model: ScvdComponentViewer | undefined; + private _memUsageStart: number = 0; + private _memUsageLast: number = 0; + private _timeUsageLast: number = 0; + private _statementEngine: StatementEngine | undefined; + private _guiTree: ScvdGuiTree | undefined; + + public constructor( + ) { + } + + private injectLineNumbers(xml: string): string { + const lines = xml.split(/\r?\n/); + const result: string[] = []; + for (const [idx, line] of lines.entries()) { + result.push(line.replace( + /<((?!\/|!|\?)([A-Za-z_][A-Za-z0-9._:-]*))/g, + `<$1 __line="${idx + 1}"` + )); + } + return result.join('\n'); + } + + public getGuiTree(): ScvdGuiTree[] | undefined { + return this._guiTree?.children; + } + + public getStats(text: string): string { + const mem = process.memoryUsage(); + const memCurrent = Math.round(mem.heapUsed / 1024 / 1024); + const timeCurrent = Date.now(); + + if (this._timeUsageLast === 0) { + this._timeUsageLast = timeCurrent; + } + if (this._memUsageStart === 0) { + this._memUsageStart = memCurrent; + this._memUsageLast = memCurrent; + } + + const memUsage = memCurrent - this._memUsageLast; + const timeUsage = timeCurrent - this._timeUsageLast; + const memIncrease = memCurrent - this._memUsageStart; + + this._memUsageLast = memCurrent; + this._timeUsageLast = timeCurrent; + + return `${text}, Time: ${timeUsage} ms, Mem: ${memUsage}, Mem Increase: ${memIncrease} MB, (Total: ${memCurrent} MB)`; + } + + public async readModel(filename: URI, debugSession: GDBTargetDebugSession, debugTracker: GDBTargetDebugTracker): Promise { + const stats: string[] = []; + + stats.push(this.getStats(` Start reading SCVD file ${filename}`)); + const buf = (await this.readFileToBuffer(filename)).toString('utf-8'); + stats.push(this.getStats(' read')); + const bufLineNo = this.injectLineNumbers(buf); + stats.push(this.getStats(' inject')); + const xml: Json = await this.parseXml(bufLineNo); + stats.push(this.getStats(' parse')); + + if (xml === undefined) { + console.error('Failed to parse SCVD XML'); + return; + } + + ScvdBase.resetIds(); + this.model = new ScvdComponentViewer(undefined); + if (!this.model) { + console.error('Failed to create SCVD model'); + return; + } + + this.model.readXml(xml); + stats.push(this.getStats(' model.readXml')); + + const scvdEvalContext = new ScvdEvalContext(this.model); + scvdEvalContext.init(debugSession, debugTracker); + stats.push(this.getStats(' evalContext.init')); + + const executionContext = scvdEvalContext.getExecutionContext(); + this.model.setExecutionContextAll(executionContext); + stats.push(this.getStats(' model.setExecutionContextAll')); + + this.model.configureAll(); + stats.push(this.getStats(' model.configureAll')); + this.model.validateAll(true); + stats.push(this.getStats(' model.validateAll')); + + const resolver = new Resolver(this.model); + resolver.resolve(); + stats.push(this.getStats(' resolver.resolve')); + + await this.model.calculateTypedefs(); + stats.push(this.getStats(' model.calculateTypedefs')); + + this.statementEngine = new StatementEngine(this.model, executionContext); + this.statementEngine.initialize(); + stats.push(this.getStats(' statementEngine.initialize')); + this._guiTree = new ScvdGuiTree(undefined, 'component-viewer-root'); + + console.log('ComponentViewerInstance readModel stats:\n' + stats.join('\n ')); + } + + public async update(): Promise { + const stats: string[] = []; + stats.push(this.getStats(' start')); + if (this._statementEngine === undefined || this._guiTree === undefined) { + return; + } + const epoch = this._guiTree.beginUpdate(); + await this._statementEngine.executeAll(this._guiTree); + this._guiTree.finalizeUpdate(epoch); + stats.push(this.getStats('end')); + console.log('ComponentViewerInstance update stats:\n' + stats.join('\n ')); + } + + private async readFileToBuffer(filePath: URI): Promise { + try { + const buffer = await vscode.workspace.fs.readFile(filePath); + return Buffer.from(buffer); + } catch (error) { + console.error('Error reading file:', error); + throw error; + } + } + + private async parseXml(text: string) { + try { + const json = await parseStringPromise(text, xmlOpts); + //console.log(JSON.stringify(json, null, 2)); + return json; + } catch (err) { + console.error('Error parsing XML:', err); + } + } + + public get model(): ScvdComponentViewer | undefined { + return this._model; + } + + private set model(value: ScvdComponentViewer | undefined) { + this._model = value; + } + + public get statementEngine(): StatementEngine | undefined { + return this._statementEngine; + } + + private set statementEngine(value: StatementEngine | undefined) { + this._statementEngine = value; + } + + public async executeStatements(guiTree: ScvdGuiTree): Promise { + if (this._statementEngine !== undefined) { + await this._statementEngine.executeAll(guiTree); + } + } +} diff --git a/src/views/component-viewer/component-viewer-logger.ts b/src/views/component-viewer/component-viewer-logger.ts new file mode 100644 index 00000000..2a7f4d30 --- /dev/null +++ b/src/views/component-viewer/component-viewer-logger.ts @@ -0,0 +1,20 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import * as manifest from '../../manifest'; + +export const componentViewerLogger = vscode.window.createOutputChannel(manifest.COMPONENT_VIEWER_DISPLAY_NAME, { log: true }); diff --git a/src/views/component-viewer/component-viewer-main.ts b/src/views/component-viewer/component-viewer-main.ts new file mode 100644 index 00000000..ff6c2634 --- /dev/null +++ b/src/views/component-viewer/component-viewer-main.ts @@ -0,0 +1,185 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import { GDBTargetDebugTracker, GDBTargetDebugSession, SessionStackItem } from '../../debug-session'; +import { ComponentViewerInstance } from './component-viewer-instance'; +import { URI } from 'vscode-uri'; +import { ComponentViewerTreeDataProvider } from './component-viewer-tree-view'; + + +export class ComponentViewer { + private _activeSession: GDBTargetDebugSession | undefined; + private _instances: ComponentViewerInstance[] = []; + private _componentViewerTreeDataProvider: ComponentViewerTreeDataProvider | undefined; + private _context: vscode.ExtensionContext; + private _instanceUpdateCounter: number = 0; + private _updateSemaphoreFlag: boolean = false; + private _loadingCounter: number = 0; + + public constructor(context: vscode.ExtensionContext) { + this._context = context; + } + + public activate(tracker: GDBTargetDebugTracker): void { + /* Create Tree Viewer */ + this._componentViewerTreeDataProvider = new ComponentViewerTreeDataProvider(); + const treeProviderDisposable = vscode.window.registerTreeDataProvider('cmsis-debugger.componentViewer', this._componentViewerTreeDataProvider); + this._context.subscriptions.push( + treeProviderDisposable); + // Subscribe to debug tracker events to update active session + this.subscribetoDebugTrackerEvents(this._context, tracker); + } + + protected async readScvdFiles(tracker: GDBTargetDebugTracker,session?: GDBTargetDebugSession): Promise { + if (!session) { + return; + } + const cbuildRunReader = await session.getCbuildRun(); + if (!cbuildRunReader) { + return; + } + // Get SCVD file paths from cbuild-run reader + const scvdFilesPaths: string [] = cbuildRunReader.getScvdFilePaths(); + if (scvdFilesPaths.length === 0) { + return undefined; + } + const cbuildRunInstances: ComponentViewerInstance[] = []; + for (const scvdFilePath of scvdFilesPaths) { + const instance = new ComponentViewerInstance(); + if (this._activeSession !== undefined) { + await instance.readModel(URI.file(scvdFilePath), this._activeSession, tracker); + cbuildRunInstances.push(instance); + } + } + this._instances = cbuildRunInstances; + } + + private async loadCbuildRunInstances(session: GDBTargetDebugSession, tracker: GDBTargetDebugTracker) : Promise { + this._loadingCounter++; + console.log(`Loading SCVD files from cbuild-run, attempt #${this._loadingCounter}`); + // Try to read SCVD files from cbuild-run file first + await this.readScvdFiles(tracker, session); + // Are there any SCVD files found in cbuild-run? + if (this._instances.length > 0) { + await this.updateInstances(); + return; + } + } + + private subscribetoDebugTrackerEvents(context: vscode.ExtensionContext, tracker: GDBTargetDebugTracker): void { + const onWillStopSessionDisposable = tracker.onWillStopSession(async (session) => { + await this.handleOnWillStopSession(session); + }); + const onConnectedDisposable = tracker.onConnected(async (session) => { + await this.handleOnConnected(session, tracker); + }); + const onDidChangeActiveStackItemDisposable = tracker.onDidChangeActiveStackItem(async (stackTraceItem) => { + await this.handleOnDidChangeActiveStackItem(stackTraceItem); + }); + const onDidChangeActiveDebugSessionDisposable = tracker.onDidChangeActiveDebugSession(async (session) => { + await this.handleOnDidChangeActiveDebugSession(session); + }); + const onStopped = tracker.onStopped(async (session) => { + await this.handleOnStopped(session.session); + }); + // clear all disposables on extension deactivation + context.subscriptions.push( + onWillStopSessionDisposable, + onConnectedDisposable, + onDidChangeActiveStackItemDisposable, + onDidChangeActiveDebugSessionDisposable, + onStopped + ); + } + + private async handleOnStopped(session: GDBTargetDebugSession): Promise { + // Clear active session if it is NOT the one being stopped + if (this._activeSession?.session.id !== session.session.id) { + this._activeSession = undefined; + } + // Update component viewer instance(s) + await this.updateInstances(); + } + + private async handleOnWillStopSession(session: GDBTargetDebugSession): Promise { + // Clear active session if it is the one being stopped + if (this._activeSession?.session.id === session.session.id) { + this._activeSession = undefined; + } + // Update component viewer instance(s) + await this.updateInstances(); + } + + private async handleOnConnected(session: GDBTargetDebugSession, tracker: GDBTargetDebugTracker): Promise { + // if new session is not the current active session, erase old instances and read the new ones + if (this._activeSession?.session.id !== session.session.id) { + this._instances = []; + this._componentViewerTreeDataProvider?.deleteModels(); + } + // Update debug session + this._activeSession = session; + // Load SCVD files from cbuild-run + await this.loadCbuildRunInstances(session, tracker); + // Subscribe to refresh events of the started session + session.refreshTimer.onRefresh(async (refreshSession) => { + if (this._activeSession?.session.id === refreshSession.session.id) { + // Update component viewer instance(s) + await this.updateInstances(); + } + }); + } + + private async handleOnDidChangeActiveStackItem(stackTraceItem: SessionStackItem): Promise { + if ((stackTraceItem.item as vscode.DebugStackFrame).frameId !== undefined) { + // Update instance(s) with new stack frame info + await this.updateInstances(); + } + } + + private async handleOnDidChangeActiveDebugSession(session: GDBTargetDebugSession | undefined): Promise { + // Update debug session + this._activeSession = session; + // Update component viewer instance(s) + await this.updateInstances(); + } + + private async updateInstances(): Promise { + if (this._updateSemaphoreFlag) { + return; + } + this._updateSemaphoreFlag = true; + this._instanceUpdateCounter = 0; + if (!this._activeSession) { + this._componentViewerTreeDataProvider?.deleteModels(); + this._updateSemaphoreFlag = false; + return; + } + if (this._instances.length === 0) { + this._updateSemaphoreFlag = false; + return; + } + this._componentViewerTreeDataProvider?.resetModelCache(); + for (const instance of this._instances) { + this._instanceUpdateCounter++; + console.log(`Updating Component Viewer Instance #${this._instanceUpdateCounter}`); + await instance.update(); + this._componentViewerTreeDataProvider?.addGuiOut(instance.getGuiTree()); + } + this._componentViewerTreeDataProvider?.showModelData(); + this._updateSemaphoreFlag = false; + } +} diff --git a/src/views/component-viewer/component-viewer-target-access.ts b/src/views/component-viewer/component-viewer-target-access.ts new file mode 100644 index 00000000..16feb508 --- /dev/null +++ b/src/views/component-viewer/component-viewer-target-access.ts @@ -0,0 +1,195 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as vscode from 'vscode'; +import { DebugProtocol } from '@vscode/debugprotocol'; +import { GDBTargetDebugSession } from '../../debug-session'; +import { logger } from '../../logger'; + + +export class ComponentViewerTargetAccess { + _activeSession: GDBTargetDebugSession | undefined; + constructor () { + if (vscode.debug.activeDebugSession) { + this._activeSession = new GDBTargetDebugSession(vscode.debug.activeDebugSession); + } + } + + // Function to reset active session + public setActiveSession(session: GDBTargetDebugSession): void { + this._activeSession = session; + } + + public async evaluateSymbolAddress(address: string, context = 'hover'): Promise { + try { + const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; + const args: DebugProtocol.EvaluateArguments = { + expression: `&${address}`, + frameId, // Currently required by CDT GDB Adapter + context: context + }; + const response = await this._activeSession?.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body']; + return response.result.split(' ')[0]; // Return only the address part + } catch (error: unknown) { + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to evaluate address '${address}' - '${errorMessage}'`); + return undefined; + } + } + + private formatAddress(address: string | number | bigint): string { + const raw = typeof address === 'string' ? address.trim() : address.toString(); + if (raw.length === 0) { + return raw; + } + if (raw.startsWith('0x') || raw.startsWith('0X')) { + return raw; + } + + const numericAddress = typeof address === 'bigint' ? address : Number(raw); + if (typeof numericAddress === 'number' && Number.isNaN(numericAddress)) { + return raw; + } + + const asHex = typeof numericAddress === 'bigint' ? numericAddress.toString(16) : numericAddress.toString(16); + return `0x${asHex}`; + } + + public async evaluateSymbolName(address: string | number | bigint, context = 'hover'): Promise { + try { + const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; + const formattedAddress = this.formatAddress(address); + const args: DebugProtocol.EvaluateArguments = { + expression: `(unsigned int*)${formattedAddress}`, + frameId, // Currently required by CDT GDB Adapter + context: context + }; + const response = await this._activeSession?.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body']; + const resultText = response?.result.split('<')[1]?.split('>')[0].trim(); + if (!resultText || resultText.startsWith('No symbol matches')) { + return undefined; + } + + return resultText; + } catch (error: unknown) { + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to evaluate name '${address}' - '${errorMessage}'`); + return undefined; + } + } + + public async evaluateSymbolContext(address: string, context = 'hover'): Promise { + try { + const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; + const formattedAddress = this.formatAddress(address); + // Ask GDB for file/line context of the address. + const args: DebugProtocol.EvaluateArguments = { + expression: `info line *${formattedAddress}`, + frameId, + context + }; + const response = await this._activeSession?.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body']; + const resultText = response?.result; + if (!resultText || resultText.startsWith('No line information')) { + return undefined; + } + return resultText.trim(); + } catch (error: unknown) { + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to evaluate context for '${address}' - '${errorMessage}'`); + return undefined; + } + } + + public async evaluateSymbolSize(symbol: string, context = 'hover'): Promise { + try { + const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; + const args: DebugProtocol.EvaluateArguments = { + expression: `sizeof(${symbol})`, + frameId, + context + }; + const response = await this._activeSession?.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body']; + const raw = response?.result; + const parsed = Number(raw); + if (Number.isFinite(parsed)) { + return parsed; + } + return undefined; + } catch (error: unknown) { + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to evaluate size of '${symbol}' - '${errorMessage}'`); + return undefined; + } + } + + public async evaluateMemory(address: string, length: number, offset: number): Promise { + try { + const args: DebugProtocol.ReadMemoryArguments = { + memoryReference: `${address}`, + count: length, + offset: offset + }; + const response = await this._activeSession?.session.customRequest('readMemory', args) as DebugProtocol.ReadMemoryResponse['body']; + return response?.data; + } catch (error: unknown) { + // Change address to hex format for better logging + const hexAddress = `0x${Number(address).toString(16).toUpperCase()}`; + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to read memory at address '${hexAddress}' - '${errorMessage}'`); + return undefined; + } + } + + public async evaluateNumberOfArrayElements(symbol: string): Promise { + try { + const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; + const args: DebugProtocol.EvaluateArguments = { + expression: `sizeof(${symbol})/sizeof(${symbol}[0])`, + frameId, // Currently required by CDT GDB Adapter + context: 'hover' + }; + const response = await this._activeSession?.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body']; + const resultText = response?.result.trim(); + const numElements = Number(resultText); + if (Number.isNaN(numElements)) { + return undefined; + } + return numElements; + } catch (error: unknown) { + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to evaluate number of elements for array '${symbol}' - '${errorMessage}'`); + return undefined; + } + } + + public async evaluateRegisterValue(register: string): Promise { + try { + const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0; + const args: DebugProtocol.EvaluateArguments = { + expression: `$${register}`, + frameId, // Currently required by CDT GDB Adapter + context: 'hover' + }; + const response = await this._activeSession?.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body']; + return response.result; + } catch (error: unknown) { + const errorMessage = (error as Error)?.message; + logger.debug(`Session '${this._activeSession?.session.name}': Failed to evaluate register value for '${register}' - '${errorMessage}'`); + return undefined; + } + } +} diff --git a/src/views/component-viewer/component-viewer-tree-view.ts b/src/views/component-viewer/component-viewer-tree-view.ts new file mode 100755 index 00000000..68bd5b2e --- /dev/null +++ b/src/views/component-viewer/component-viewer-tree-view.ts @@ -0,0 +1,90 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import * as vscode from 'vscode'; +import { ScvdGuiInterface } from './model/scvd-gui-interface'; + + +export class ComponentViewerTreeDataProvider implements vscode.TreeDataProvider { + private readonly _onDidChangeTreeData = new vscode.EventEmitter(); + public readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + private _objectOutRoots: ScvdGuiInterface[] = []; + private _scvdModel: ScvdGuiInterface[] = []; + + constructor () { + } + + public activate(): void { + this.addRootObject(); + this.refresh(); + } + + public getTreeItem(element: ScvdGuiInterface): vscode.TreeItem { + const treeItemLabel = element.getGuiName() ?? 'UNKNOWN'; + const treeItem = new vscode.TreeItem(treeItemLabel); + treeItem.collapsibleState = element.hasGuiChildren() + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None; + // Needs fixing, getGuiValue() for ScvdNode returns 0 when undefined + treeItem.description = element.getGuiValue() ?? ''; + treeItem.tooltip = element.getGuiLineInfo() ?? ''; + treeItem.id = (element as unknown as { nodeId: string }).nodeId; + return treeItem; + } + + public getChildren(element?: ScvdGuiInterface): Promise { + if (!element) { + return Promise.resolve(this._objectOutRoots); + } + + const children = element.getGuiChildren() || []; + return Promise.resolve(children); + } + + private refresh(): void { + this._onDidChangeTreeData.fire(); + } + + public resetModelCache(): void { + this._scvdModel = []; + this._objectOutRoots = []; + } + + public addGuiOut(guiOut: ScvdGuiInterface[] | undefined) { + if (guiOut !== undefined) { + guiOut.forEach(item => this._scvdModel.push(item)); + } + } + + public showModelData() { + this.addRootObject(); + this.refresh(); + } + + public deleteModels() { + this._scvdModel = []; + this._objectOutRoots = []; + this.refresh(); + } + + private addRootObject(): void { + if (this._scvdModel.length === 0) { + return; + } + this._objectOutRoots = [...this._scvdModel]; + } +} diff --git a/src/views/component-viewer/data-host/access-host.ts b/src/views/component-viewer/data-host/access-host.ts new file mode 100644 index 00000000..2589738a --- /dev/null +++ b/src/views/component-viewer/data-host/access-host.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import type { EvalValue, RefContainer } from '../parser-evaluator/ref-container'; + +// Concrete data I/O (memory/register access). +export interface DataAccessHost { + // Value access acts on container.{anchor,offsetBytes,widthBytes} + readValue(container: RefContainer): Promise; // may return undefined -> error + writeValue(container: RefContainer, value: EvalValue): Promise; // may return undefined -> error +} diff --git a/src/views/component-viewer/data-host/memory-host.ts b/src/views/component-viewer/data-host/memory-host.ts new file mode 100644 index 00000000..0d04249e --- /dev/null +++ b/src/views/component-viewer/data-host/memory-host.ts @@ -0,0 +1,490 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import { EvalValue, RefContainer } from '../parser-evaluator/model-host'; +import { ValidatingCache } from './validating-cache'; + +export class MemoryContainer { + constructor( + readonly symbolName: string + ){ } + private buf: Uint8Array | null = null; + private winStart = 0; + private winSize = 0; + + private store: Uint8Array = new Uint8Array(0); + + private ensure(off: number, size: number) { + // Grow the local store if needed so [off, off+size) fits. + const needed = off + size; + if (this.store.length < needed) { + const next = new Uint8Array(needed); + next.set(this.store, 0); + this.store = next; + } + + // If our current window already covers the requested range, we're done. + if (this.buf && off >= this.winStart && off + size <= this.winStart + this.winSize) { + return; + } + + // Point the window at the requested range in the local store. + this.buf = this.store.subarray(off, off + size); + this.winStart = off; + this.winSize = size; + } + + public get byteLength(): number { + return this.store.length; + } + + public read(off: number, size: number): Uint8Array { + this.ensure(off, size); + if (!this.buf) { + console.error('window not initialized'); + return new Uint8Array(size); + } + const rel = off - this.winStart; + return this.buf.subarray(rel, rel + size); + } + + // allow writing with optional zero padding to `virtualSize` + public write(off: number, data: Uint8Array, virtualSize?: number): void { + const total = virtualSize !== undefined ? Math.max(virtualSize, data.length) : data.length; + this.ensure(off, total); + if (!this.buf) { + console.error('window not initialized'); + return; + } + const rel = off - this.winStart; + + // write the payload + this.buf.set(data, rel); + + // zero-fill up to total + const extra = total - data.length; + if (extra > 0) { + this.buf.fill(0, rel + data.length, rel + total); + } + } + + public clear(): void { + this.buf = null; + this.winStart = 0; + this.winSize = 0; + this.store = new Uint8Array(0); + } +} + +// --- helpers (LE encoding) --- +function leToNumber(bytes: Uint8Array): number { + let out = 0; + for (const b of Array.from(bytes).reverse()) { + out = (out << 8) | (b & 0xff); + } + return out >>> 0; +} +function leIntToBytes(v: number, size: number): Uint8Array { + const out = new Uint8Array(size); + let tmp = v >>> 0; + for (let i = 0; i < size; i++) { + out.set([tmp & 0xff], i); + tmp >>>= 8; + } + return out; +} + +export type Endianness = 'little'; +export interface HostOptions { endianness?: Endianness; } + +type ElementMeta = { + offsets: number[]; // append offsets within the symbol + sizes: number[]; // logical size (actualSize) per append + bases: number[]; // target base address per append + elementSize?: number; // known uniform stride when consistent +}; + +// The piece your host delegates to for readValue/writeValue. +export class MemoryHost { + private cache = new ValidatingCache(); + private endianness: Endianness; + private elementMeta = new Map(); + + private getOrInitMeta(name: string): ElementMeta { + const existing = this.elementMeta.get(name); + if (!existing) { + // with exactOptionalPropertyTypes: do NOT assign elementSize: undefined + const created: ElementMeta = { offsets: [], sizes: [], bases: [] }; + this.elementMeta.set(name, created); + return created; + } + return existing; + } + + // normalize number → safe JS number for addresses + private toAddrNumber(x: number): number | undefined { + if (!Number.isFinite(x) || x < 0 || !Number.isSafeInteger(x)) { + console.error(`invalid target base address (number): ${x}`); + return undefined; + } + return x; + } + + constructor() { + this.endianness = 'little'; + } + + private getContainer(varName: string): MemoryContainer { + return this.cache.ensure(varName, () => new MemoryContainer(varName), false); + } + + // Read a value, using byte-only offsets and widths. + public async readValue(ref: RefContainer): Promise { + const variableName = ref.anchor?.name; + const widthBytes = ref.widthBytes ?? 0; + if (!variableName || widthBytes <= 0) { + return undefined; + } + + const container = this.getContainer(variableName); + const byteOff = ref.offsetBytes ?? 0; + + const raw = container.read(byteOff, widthBytes); + + if (this.endianness !== 'little') { + // TOIMPL: add BE support if needed + } + + // Interpret the bytes: + // - float kinds decode as float32/float64 + // - ≤4 bytes: JS number (uint32) + // - 8 bytes: BigInt for full 64-bit integer fidelity + // - >8 bytes: return a copy of the raw bytes + if (ref.valueType?.kind === 'float') { + if (widthBytes === 4) { + const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength); + return dv.getFloat32(0, true); + } + if (widthBytes === 8) { + const dv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength); + return dv.getFloat64(0, true); + } + } + if (widthBytes <= 4) { + return leToNumber(raw); + } + if (widthBytes === 8) { + let out = 0n; + for (let i = 0; i < 8; i++) { + // raw is a Uint8Array; indexed access is safe here. + // eslint-disable-next-line security/detect-object-injection + out |= BigInt(raw[i]) << BigInt(8 * i); + } + return out; + } + // for larger widths, return a copy of the bytes + return raw.slice(); + } + + // Read raw bytes without interpretation. + public async readRaw(ref: RefContainer, size: number): Promise { + const variableName = ref.anchor?.name; + if (!variableName || size <= 0) { + return undefined; + } + const container = this.getContainer(variableName); + const byteOff = ref.offsetBytes ?? 0; + return container.read(byteOff, size).slice(); + } + + // Write a value, using byte-only offsets and widths. + public async writeValue(ref: RefContainer, value: EvalValue, virtualSize?: number): Promise { + const variableName = ref.anchor?.name; + const widthBytes = ref.widthBytes ?? 0; + if (!variableName || widthBytes <= 0) { + return; + } + + const container = this.getContainer(variableName); + const byteOff = ref.offsetBytes ?? 0; + + let buf: Uint8Array; + + if (value instanceof Uint8Array) { + if (value.length === widthBytes) { + buf = value; + } else { + // truncate or pad to widthBytes + buf = new Uint8Array(widthBytes); + buf.set(value.subarray(0, widthBytes), 0); + } + } else { + // normalize value to number then to bytes + let valNum: number | bigint; + if (typeof value === 'boolean') { + valNum = value ? 1 : 0; + } else if (typeof value === 'number') { + valNum = Math.trunc(value); + } else if (typeof value === 'bigint') { + valNum = value; + } else { + console.error('writeValue: unsupported value type'); + return; + } + + if (typeof valNum === 'bigint') { + buf = new Uint8Array(widthBytes); + let tmp = valNum; + for (let i = 0; i < widthBytes; i++) { + // Indexing into a Uint8Array is safe here. + // eslint-disable-next-line security/detect-object-injection + buf[i] = Number(tmp & 0xFFn); + tmp >>= 8n; + } + } else { + buf = leIntToBytes(valNum, widthBytes); + } + } + + if (virtualSize !== undefined && virtualSize < widthBytes) { + console.error(`writeValue: virtualSize (${virtualSize}) must be >= widthBytes (${widthBytes})`); + return; + } + + const total = virtualSize ?? widthBytes; + container.write(byteOff, buf, total); + } + + public setVariable( + name: string, + size: number, + value: number | bigint | Uint8Array, + offset: number, // NEW: controls where to place the data + targetBase?: number, // target base address where it was read from + virtualSize?: number, // total logical bytes for this element (>= size) + ): void { + if (!Number.isSafeInteger(offset)) { + console.error(`setVariable: offset must be a safe integer, got ${offset}`); + return; + } + + const container = this.getContainer(name); + + // Decide where to write: + // - offset === -1 → append at the end + // - otherwise → write at the given offset + const appendOff = offset === -1 ? (container.byteLength ?? 0) : offset; + if (appendOff < 0) { + console.error(`setVariable: offset must be >= 0 or -1, got ${offset}`); + return; + } + + // normalize payload to exactly `size` bytes (numbers LE-encoded) + let buf: Uint8Array; + if (typeof value === 'number') { + buf = leIntToBytes(Math.trunc(value), size); + } else if (typeof value === 'bigint') { + buf = new Uint8Array(size); + let tmp = value; + for (let i = 0; i < size; i++) { + // Indexing into a Uint8Array is safe here. + // eslint-disable-next-line security/detect-object-injection + buf[i] = Number(tmp & 0xffn); + tmp >>= 8n; + } + } else if (value instanceof Uint8Array) { + // Avoid an extra allocation when already the right size + buf = value.length === size ? value : new Uint8Array(value.subarray(0, size)); + } else { + console.error('setVariable: unsupported value type'); + return; + } + + if (virtualSize !== undefined && virtualSize < size) { + console.error(`setVariable: virtualSize (${virtualSize}) must be >= size (${size})`); + return; + } + const total = virtualSize ?? size; + + // write and zero-pad to `total`, extends as needed + container.write(appendOff, buf, total); + + // record per-append metadata + const meta = this.getOrInitMeta(name); + meta.offsets.push(appendOff); + meta.sizes.push(total); + const normBase = targetBase !== undefined ? this.toAddrNumber(targetBase) : 0; + meta.bases.push(normBase !== undefined ? normBase : 0); + + // maintain uniform stride when consistent + if (meta.elementSize === undefined && meta.sizes.length === 1) { + meta.elementSize = total; // first append sets stride + } else if (meta.elementSize !== undefined && meta.elementSize !== total) { + delete meta.elementSize; // mixed sizes → remove the optional prop + } + + this.cache.set(name, container, true); + } + + /** + * Get bytes previously recorded for a symbol. + * + * - No args → whole symbol. + * - `offset` only → best-effort element at that offset (using metadata), + * or `[offset .. end)` if no matching element exists. + * - `offset` + `size` → exact range `[offset .. offset+size)`. + */ + public getVariable(name: string, size?: number, offset?: number): number | undefined { + const container = this.getContainer(name); + const totalBytes = container.byteLength ?? 0; + + if (totalBytes === 0) { + // symbol exists but has no data yet + return undefined; + } + + const off = offset ?? 0; + if (!Number.isSafeInteger(off) || off < 0) { + return undefined; + } + if (off >= totalBytes) { + return undefined; + } + + let spanSize: number; + + if (size !== undefined) { + // explicit size wins + if (!Number.isSafeInteger(size) || size <= 0) { + return undefined; + } + spanSize = size; + } else { + // infer size from metadata if possible + const meta = this.elementMeta.get(name); + if (meta) { + const idx = meta.offsets.indexOf(off); + if (idx >= 0 && idx < meta.sizes.length) { + spanSize = meta.sizes.at(idx) as number; + } else { + // no matching element → default to [off .. end) + spanSize = totalBytes - off; + } + } else { + // no metadata at all → [off .. end) + spanSize = totalBytes - off; + } + } + + if (off + spanSize > totalBytes) { + return undefined; + } + + if (spanSize > 4) { + return undefined; + } + + // read() returns a view; return a copy so callers can't mutate our backing store + const raw = container.read(off, spanSize).slice(); + return leToNumber(raw); + } + + public invalidate(name?: string): void { + if (name === undefined) { + this.cache.invalidateAll(); + } else { + this.cache.invalidate(name); + } + } + + public clearVariable(name: string): boolean { + this.elementMeta.delete(name); + const container = this.cache.get(name); + if (container?.clear) { + container.clear(); + } + return this.cache.delete(name); + } + + public clear(): void { + this.elementMeta.clear(); + this.cache.clear(); + } + + // Number of array elements recorded for `name`. Defaults to 1 when unknown. + public getArrayElementCount(name: string): number { + const m = this.elementMeta.get(name); + const n = m?.offsets.length ?? 0; + return n > 0 ? n : 1; + } + + // All recorded target base addresses (per append) for `name`. + public getArrayTargetBases(name: string): (number | undefined)[] { + const m = this.elementMeta.get(name); + return m ? m.bases.slice() : []; + } + + // Target base address for element `index` of `name` (number | undefined). + public getElementTargetBase(name: string, index: number): number | undefined { + const m = this.elementMeta.get(name); + if (!m) { + console.error(`getElementTargetBase: unknown symbol "${name}"`); + return undefined; + } + if (index < 0 || index >= m.bases.length) { + console.error(`getElementTargetBase: index ${index} out of range for "${name}"`); + return undefined; + } + return m.bases.at(index); + } + + // Optional: repair or set an address later. + public setElementTargetBase(name: string, index: number, base: number): void { + const m = this.elementMeta.get(name); + if (!m) { + console.error(`setElementTargetBase: unknown symbol "${name}"`); + return; + } + if (index < 0 || index >= m.bases.length) { + console.error(`setElementTargetBase: index ${index} out of range for "${name}"`); + return; + } + const norm = this.toAddrNumber(base); + if (norm !== undefined) { + m.bases.fill(norm, index, index + 1); + } + } + + // Optional: if you sometimes need to infer a count from bytes for legacy data + public getArrayLengthFromBytes(name: string): number { + const m = this.elementMeta.get(name); + if (!m) { + return 1; + } + if (m.offsets.length > 0) { + return m.offsets.length; + } + + const container = this.getContainer(name); + const totalBytes = container.byteLength ?? 0; + const stride = m.elementSize; + if (!stride || stride <= 0) { + return 1; + } + return Math.max(1, Math.floor(totalBytes / stride)); + } +} diff --git a/src/views/component-viewer/data-host/register-host.ts b/src/views/component-viewer/data-host/register-host.ts new file mode 100644 index 00000000..32914215 --- /dev/null +++ b/src/views/component-viewer/data-host/register-host.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import { ValidatingCache } from './validating-cache'; + +function normalize(name: string): string { + return name.trim().toUpperCase(); +} + +function toUint32(value: number | bigint): number | bigint { + if (typeof value === 'bigint') { + return value & 0xFFFFFFFFn; + } + return value >>> 0; +} + +export class RegisterHost { + private cache = new ValidatingCache(normalize); + + public read(name: string): number | bigint | undefined { + if (!name) { + console.error('RegisterHost: read: empty register name'); + return undefined; + } + return this.cache.get(name); + } + + public write(name: string, value: number | bigint): number | bigint | undefined { + if (!name) { + console.error('RegisterHost: write: empty register name'); + return undefined; + } + this.cache.set(name, toUint32(value)); + return value; + } + + public invalidate(name: string): void { + this.cache.invalidate(name); + } + + public clear(): void { + this.cache.clear(); + } + +} diff --git a/src/views/component-viewer/data-host/validating-cache.ts b/src/views/component-viewer/data-host/validating-cache.ts new file mode 100644 index 00000000..788d30c8 --- /dev/null +++ b/src/views/component-viewer/data-host/validating-cache.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +export interface ValidatingEntry { value: T; valid: boolean; } + +export class ValidatingCache { + private map = new Map>(); + + public constructor(private normalize: (key: string) => string = (k) => k) {} + + public get(key: string): T | undefined { + if (!key) { + return undefined; + } + const norm = this.normalize(key); + const entry = this.map.get(norm); + return entry && entry.valid ? entry.value : undefined; + } + + public set(key: string, value: T, valid = true): void { + if (!key) { + return; + } + const norm = this.normalize(key); + this.map.set(norm, { value, valid }); + } + + public ensure(key: string, factory: () => T, valid = true): T { + const norm = this.normalize(key); + const existing = this.map.get(norm); + if (existing) { + return existing.value; + } + const value = factory(); + this.map.set(norm, { value, valid }); + return value; + } + + public invalidate(key: string): void { + if (!key) { + return; + } + const norm = this.normalize(key); + const entry = this.map.get(norm); + if (entry) { + entry.valid = false; + this.map.set(norm, entry); + } + } + + public invalidateAll(): void { + this.map.forEach((entry, key) => { + entry.valid = false; + this.map.set(key, entry); + }); + } + + public clear(): void { + this.map.clear(); + } + + public delete(key: string): boolean { + if (!key) { + return false; + } + const norm = this.normalize(key); + return this.map.delete(norm); + } +} diff --git a/src/views/component-viewer/model/number-type.ts b/src/views/component-viewer/model/number-type.ts new file mode 100644 index 00000000..77cdcaf4 --- /dev/null +++ b/src/views/component-viewer/model/number-type.ts @@ -0,0 +1,349 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as utils from './scvd-utils'; + +export enum NumFormat { + undefined = 0, + decimal = 10, + hexadecimal = 16, + octal = 8, + binary = 2, + boolean = 1, +} + +interface NumContainer { + value: number; + numFormat: NumFormat; + displayFormat: NumFormat; + numOfDigits: number; + numOfDisplayBits: number; + numMin?: number; // optional, used for min/max values + numMax?: number; // optional, used for min/max values +} + +export type NumberTypeInput = number | NumberType | string; + +export class NumberType { + private _value: NumContainer = { + value: 0, + numFormat: NumFormat.undefined, + displayFormat: NumFormat.undefined, + numOfDigits: 1, + numOfDisplayBits: 1 + }; + + constructor(val?: NumberTypeInput, numFormat?: NumFormat, numOfDisplayBits?: number) { + + if (typeof val === 'number') { + this._value.value = val; + if (numFormat !== undefined) { + this._value.numFormat = numFormat; + } else { + this._value.numFormat = NumFormat.decimal; + } + this._value.displayFormat = this._value.numFormat; + if ( numOfDisplayBits !== undefined) { + this._value.numOfDisplayBits = numOfDisplayBits; + } + } else if (val instanceof NumberType) { + this._value = { value: val.value, numFormat: val.format, displayFormat: val.displayFormat, numOfDigits: val._value.numOfDigits, numOfDisplayBits: val._value.numOfDisplayBits }; + } else if (typeof val === 'string') { + this._value = this.toNumber(val); + } + } + + public get numOfDigits() { + return this._value.numOfDigits; + } + public set numOfDigits(value: number) { + if (value < 1) { + value = 1; + } + this._value.numOfDigits = value; + } + + public get numOfDisplayBits() { + return this._value.numOfDisplayBits; + } + public set numOfDisplayBits(value: number) { + if (value < 1) { + value = 1; + } + this._value.numOfDisplayBits = value; + } + + protected toNumber(value: string): NumContainer { + let val = value.toLowerCase(); + const num: NumContainer = { value: 0, numFormat: NumFormat.undefined, displayFormat: NumFormat.undefined, numOfDigits: 1, numOfDisplayBits: 1 }; + let pos = 0; + let isNegative = false; + if (val[0] == '-') { + val = val.substring(1); + isNegative = true; + } + + // positive values in num-string from here + if (val == '') { + num.numFormat = NumFormat.undefined; + } else if (val == 'true') { + num.numFormat = NumFormat.boolean; + num.value = 1; + } else if (val == 'false') { + num.numFormat = NumFormat.boolean; + num.value = 0; + } else if (val.match(/^0x\d*/i)) { + num.numFormat = NumFormat.hexadecimal; + num.numOfDigits = val.length - 2; + pos = 2; + } else if (val.match(/^0b\d*/i)) { + num.numFormat = NumFormat.binary; + num.numOfDigits = val.length - 2; + pos = 2; + } else if (val.match(/^0\d*/) && val.length > 1) { + num.numFormat = NumFormat.octal; + pos = 1; + } else if (val.match(/^\d*/) && val.length == 1) { + num.numFormat = NumFormat.decimal; + } else if (val.match(/^[1-9]\d*/)) { + num.numFormat = NumFormat.decimal; + } else { + num.numFormat = NumFormat.undefined; + } + + if (num.numFormat == NumFormat.undefined) { // error, try as decimal + num.numFormat = NumFormat.decimal; + } + if (num.displayFormat == NumFormat.undefined) { // not set, use original format + num.displayFormat = num.numFormat; + } + + if (num.numOfDigits < 1) { + num.numOfDigits = 1; + } + + if (num.numFormat != NumFormat.boolean) { // boolean set above + const tmp = parseInt(val.substring(pos), num.numFormat) * ((isNegative) ? -1 : 1); + if (isNaN(tmp)) { + num.numFormat = NumFormat.undefined; + } else { + num.value = tmp; + } + } + + return num; + } + + public get value(): number { + if (this._value.numMin && this._value.value < this._value.numMin) { + return this._value.numMin; + } else if (this._value.numMax && this._value.value > this._value.numMax) { + return this._value.numMax; + } + + return this._value.value; + } + + public set value(val: number | NumberType | string) { + if (typeof val === 'number') { + this._value.value = val; + if (this._value.numFormat == NumFormat.undefined) { + this._value.numFormat = NumFormat.decimal; + this._value.displayFormat = NumFormat.decimal; + } + } else if (val instanceof NumberType) { + this._value = { value: val._value.value, numFormat: val.format, displayFormat: val.displayFormat, numOfDigits: val._value.numOfDigits, numOfDisplayBits: val._value.numOfDisplayBits }; + } else if (typeof val === 'string') { + this._value = this.toNumber(val); + } + } + + public get format(): NumFormat { + return this._value.numFormat; + } + public set format(format: NumFormat) { + this._value.numFormat = format; + } + public get displayFormat(): NumFormat { + return this._value.displayFormat; + } + public set displayFormat(format: NumFormat) { + this._value.displayFormat = format; + } + public isValid(): boolean { + return this._value.numFormat != NumFormat.undefined; + } + + public getText() { + return this.getValStrByFormat(this.format, this._value.numOfDigits); + } + + public getValStrByFormat(format: NumFormat, digits: number) { + if (format == NumFormat.undefined) { + const tmp = this.value.toString(NumFormat.decimal); + return tmp.toUpperCase(); + } + + let val = this.value; + let negative = false; + if (val < 0) { + negative = true; + val *= -1; + } + + let text = ''; + if (format == NumFormat.boolean) { + switch (val) { + case 0: + text = 'false'; + break; + case 1: + default: + text = 'true'; + break; + } + } else { + text = val.toString(format); + } + + if (!text.length) { + const tmp = this.value.toString(NumFormat.decimal); + return tmp.toUpperCase(); + } + + if (format != NumFormat.boolean) { + text = text.toUpperCase(); + } + + if (digits < 1) { + digits = 1; + } + text = text.padStart(digits, '0'); + + let pos = 0; + if (negative) { + pos = 1; + text = '-' + text; + } + + text = utils.insertString(text, this.getFormatPrefix(format), pos); + + return text; + } + + + public getDisplayText() { + if (this.displayFormat == NumFormat.undefined) { + return this.getValStrByFormat(NumFormat.decimal, 1); + } + + let displayDigits = this._value.numOfDisplayBits; + switch (this._value.displayFormat) { + case NumFormat.hexadecimal: + displayDigits /= 4; // 4 bits per nibble + break; + case NumFormat.decimal: + displayDigits = 1; // print as required + break; + case NumFormat.octal: + displayDigits /= 3 ; // 3 bits per number + } + + if (displayDigits < 1) { + displayDigits = 1; + } else if (displayDigits % 1) { + displayDigits++; + } + + const text = this.getValStrByFormat(this.displayFormat, displayDigits); + + return text; + } + + public getFormatPrefix(format: NumFormat) { + switch (format) { + case NumFormat.decimal: + return ''; + case NumFormat.hexadecimal: + return '0x'; + case NumFormat.octal: + return '0'; + case NumFormat.binary: + return '0b'; + case NumFormat.undefined: + default: + return ''; + } + } + + public getFormatText(format: NumFormat) { + let text = ''; + switch (format) { + case NumFormat.decimal: { + text += 'decimal'; + } break; + case NumFormat.hexadecimal: { + text += 'hexadecimal'; + } break; + case NumFormat.octal: { + text += 'octal'; + } break; + case NumFormat.binary: { + text += 'binary'; + } break; + case NumFormat.boolean: { + text += 'boolean'; + } break; + default: + case NumFormat.undefined: { + text += 'undefined'; + } break; + } + + return text; + } + + public setMin(min: number) { + this._value.numMin = min; + } + + public setMax(max: number) { + this._value.numMax = max; + } + + public get min(): number | undefined { + return this._value.numMin; + } + + public get max(): number | undefined { + return this._value.numMax; + } + + public setMinMax(min: number | undefined, max: number | undefined) { + if (min !== undefined) { + this._value.numMin = min; + } + if (max !== undefined) { + this._value.numMax = max; + } + } + + public getMinMax(): { min: number | undefined; max: number | undefined } { + return { min: this._value.numMin, max: this._value.numMax }; + } + +} + diff --git a/src/views/component-viewer/model/scvd-base.ts b/src/views/component-viewer/model/scvd-base.ts new file mode 100644 index 00000000..a0e19533 --- /dev/null +++ b/src/views/component-viewer/model/scvd-base.ts @@ -0,0 +1,213 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Minimal core for SCVD tree nodes: parent/child wiring and basic metadata. + * Model-specific behaviour lives in ScvdNode. + */ + +export type Json = Record; + +// Constructor type used for runtime instanceof checks; kept loose to cover differing ctor shapes. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ScvdConstructor = abstract new (...args: any[]) => T; + +export abstract class ScvdBase { + private static _idNext = 0; + + private _parent: ScvdBase | undefined; + private _children: ScvdBase[] = []; + private _nodeId: number = 0; + + private _tag: string | undefined; + private _lineNo: string | undefined; + private _name: string | undefined; + private _info: string | undefined; + + private _isModified = false; + private _valid = false; + private _mustRead = true; + + public static resetIds(): void { + ScvdBase._idNext = 0; + } + + constructor(parent: ScvdBase | undefined) { + this._nodeId = ++ScvdBase._idNext; + + if (parent instanceof ScvdBase) { + this._parent = parent; + this._parent._children.push(this); + } + } + + public castToDerived(ctor: ScvdConstructor): T | undefined { + return this instanceof ctor ? (this as T) : undefined; + } + + public get parent(): ScvdBase | undefined { + return this._parent; + } + + public get children(): ScvdBase[] { + return this._children; + } + + public get nodeId(): string { + return `${this.classname}_${this._nodeId.toString()}`; + } + + public get classname(): string { + return 'ScvdBase'; + } + + public set tag(value: string | undefined) { + this._tag = value; + } + public get tag(): string | undefined { + return this._tag ?? 'Internal Object'; + } + + public get lineNo(): string | undefined { + return this._lineNo; + } + public set lineNo(value: string | undefined) { + if (value !== undefined) { + this._lineNo = value; + } + } + + public set name(name: string | undefined) { + this._name = name; + } + public get name(): string | undefined { + return this._name; + } + + public set info(text: string | undefined) { + this._info = text; + } + public get info(): string | undefined { + return this._info; + } + + public get isModified(): boolean { + return this._isModified; + } + public set isModified(value: boolean) { + this._isModified = value; + } + + public get valid(): boolean { + return this._valid; + } + public set valid(value: boolean) { + this._valid = value; + } + + public get mustRead(): boolean { + return this._mustRead; + } + public set mustRead(value: boolean) { + this._mustRead = value; + } + + public map(this: { children: TChild[] }, callbackfn: (child: TChild, index: number, array: TChild[]) => R): R[] { + return this.children.map(callbackfn); + } + + public forEach(this: { children: TChild[] }, callbackfn: (child: TChild, index: number, array: TChild[]) => void): void { + this.children.forEach(callbackfn); + } + + public filter(this: { children: TChild[] }, predicate: (child: TChild, index: number, array: TChild[]) => boolean): TChild[] { + return this.children.filter(predicate); + } + + // Symbol-context helpers – default no-op so derived classes can walk parents safely. + public addToSymbolContext(_name: string | undefined, _symbol: ScvdBase): void { + this.parent?.addToSymbolContext(_name, _symbol); + } + + public getSymbol(_name: string): ScvdBase | undefined { + return this.parent?.getSymbol(_name); + } + + public hasChildren(): boolean { + return this._children.length > 0; + } + + public configure(): boolean { + return true; + } + + public validate(prevResult: boolean): boolean { + this.valid = prevResult; + return prevResult; + } + + public reset(): boolean { + return true; + } + + private getLineNoInfo(item: ScvdBase | undefined): string | undefined { + if (item === undefined) { + return undefined; + } + const lineNo = item.lineNo; + if (lineNo === undefined) { + return this.getLineNoInfo(item.parent); + } + return lineNo; + } + + public getLineInfoStr(): string { + let lineInfo = '['; + const lineNo = this.getLineNoInfo(this); + if (lineNo !== undefined) { + lineInfo += `Line: ${lineNo} `; + } + if (this.tag !== undefined) { + lineInfo += `Tag: ${this.tag} `; + } + lineInfo += ']'; + return lineInfo; + } + + public getLineNoStr(): string { + const lineNo = this.getLineNoInfo(this); + return lineNo !== undefined ? lineNo : ''; + } + + protected sortByLine(a: T, b: T): number { + const aLineNum = Number(a.lineNo); + const bLineNum = Number(b.lineNo); + const aLine = Number.isNaN(aLineNum) ? -1 : aLineNum; + const bLine = Number.isNaN(bLineNum) ? -1 : bLineNum; + return aLine - bLine; + } + + public getDisplayLabel(): string { + const displayName = this.name ?? this.info; + if (displayName && displayName.length > 0) { + return displayName; + } + if (this.tag) { + return `${this.tag} (line ${this.getLineNoStr()})`; + } + return `${this.classname} (line ${this.getLineNoStr()})`; + } +} diff --git a/src/views/component-viewer/model/scvd-break.ts b/src/views/component-viewer/model/scvd-break.ts new file mode 100644 index 00000000..9a5df03b --- /dev/null +++ b/src/views/component-viewer/model/scvd-break.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { Json } from './scvd-base'; +import { ScvdCondition } from './scvd-condition'; +import { ScvdNode } from './scvd-node'; +import { getArrayFromJson } from './scvd-utils'; + +export class ScvdBreaks extends ScvdNode { + private _break: ScvdBreak[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdBreaks'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + const breaks = getArrayFromJson(xml.break); + breaks?.forEach( (v: Json) => { + const varItem = this.addBreak(); + varItem.readXml(v); + }); + + return super.readXml(xml); + } + + public get breaks(): ScvdBreak[] { + return this._break; + } + + public addBreak(): ScvdBreak { + const breakItem = new ScvdBreak(this); + this._break.push(breakItem); + return breakItem; + } +} + + +export class ScvdBreak extends ScvdNode { + private _cond: ScvdCondition | undefined; + + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdBreak'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.break(); + return super.readXml(xml); + } + + public set cond(value: string | undefined) { + if (value !== undefined) { + this._cond = new ScvdCondition(this, value); + return; + } + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + + public override async getConditionResult(): Promise { + if (this._cond) { + return await this._cond.getResult(); + } + return super.getConditionResult(); + } + + + private break(): void { + } +} diff --git a/src/views/component-viewer/model/scvd-calc.ts b/src/views/component-viewer/model/scvd-calc.ts new file mode 100644 index 00000000..06d53d66 --- /dev/null +++ b/src/views/component-viewer/model/scvd-calc.ts @@ -0,0 +1,90 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_calc.html + +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdCondition } from './scvd-condition'; +import { getStringFromJson, getTextBodyFromJson } from './scvd-utils'; + +export class ScvdCalc extends ScvdNode { + private _cond: ScvdCondition | undefined; + private _expression: ScvdExpression[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdCalc'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.cond = getStringFromJson(xml.cond); + + const expressions = getTextBodyFromJson(xml); + expressions?.forEach((v: string) => { + const expr = this.addExpression(v); + expr?.readXml(xml); + }); + + return super.readXml(xml); + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + + public set cond(value: string | undefined) { + if (value !== undefined) { + if ( this._cond === undefined) { + this._cond = new ScvdCondition(this, value); + return; + } + this._cond.expression = value; + } + } + + public override async getConditionResult(): Promise { + if (this._cond) { + return await this._cond.getResult(); + } + return super.getConditionResult(); + } + + public get expression(): ScvdExpression[] { + return this._expression; + } + + private addExpression(value: string | undefined): ScvdExpression | undefined { + if (value !== undefined) { + const expr = new ScvdExpression(this, value, 'expression'); + this._expression.push(expr); + return expr; + } + return undefined; + } + + +} diff --git a/src/views/component-viewer/model/scvd-component-identifier.ts b/src/views/component-viewer/model/scvd-component-identifier.ts new file mode 100644 index 00000000..d9a49956 --- /dev/null +++ b/src/views/component-viewer/model/scvd-component-identifier.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component.html + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { getStringFromJson } from './scvd-utils'; + +export class ScvdComponentIdentifier extends ScvdNode { + private _version: string | undefined; + private _shortName: string | undefined; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdComponentIdentifier'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.version = getStringFromJson(xml.version); + this.shortName = getStringFromJson(xml.shortname); + + return super.readXml(xml); + } + + public get version(): string | undefined { + return this._version; + } + public set version(version: string | undefined) { + this._version = version; + } + + public get shortName(): string | undefined { + return this._shortName; + } + public set shortName(name: string | undefined) { + this._shortName = name; + } + +} diff --git a/src/views/component-viewer/model/scvd-component-number.ts b/src/views/component-viewer/model/scvd-component-number.ts new file mode 100644 index 00000000..b23c7865 --- /dev/null +++ b/src/views/component-viewer/model/scvd-component-number.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/er_theory.html + +import { ScvdNode } from './scvd-node'; + +export class ScvdComponentNumber extends ScvdNode { + private _componentNumber: number | undefined; + + public get componentNumber(): number | undefined { + return this._componentNumber; + } + + public set componentNumber(value: number | undefined) { + this._componentNumber = value; + } + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdComponentNumber'; + } + + public getComponentRange(value: number): string | undefined { + if (value < 0 || value > 0xFF) { + return undefined; + } + if (value <= 0x3F) { + return 'User application software component'; + } + if (value <= 0x7F) { + return 'Third party middleware component'; + } + if (value <= 0xED) { + return 'MDK-Middleware component'; + } + switch (value) { + case 0xEE: + return 'Fault component'; + case 0xEF: + return 'Event statistics start/stop'; + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + return 'RTOS kernel'; + case 0xFD: + return 'Inter-process communication layer'; + case 0xFE: + return 'printf-style debug output'; + case 0xFF: + return 'Event Recorder message'; + default: + return undefined; + } + } + + + +} diff --git a/src/views/component-viewer/model/scvd-component-viewer.ts b/src/views/component-viewer/model/scvd-component-viewer.ts new file mode 100644 index 00000000..0a0e52fa --- /dev/null +++ b/src/views/component-viewer/model/scvd-component-viewer.ts @@ -0,0 +1,178 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdComponentIdentifier } from './scvd-component-identifier'; +import { ScvdEvents } from './scvd-events'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdObjects } from './scvd-object'; +import { ScvdTypedefs } from './scvd-typedef'; +import { getArrayFromJson, getObjectFromJson } from './scvd-utils'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdBreaks } from './scvd-break'; + +export class ScvdComponentViewer extends ScvdNode { + private _componentIdentifier: ScvdComponentIdentifier | undefined; + private _typedefs: ScvdTypedefs | undefined; + private _objects: ScvdObjects | undefined; + private _events: ScvdEvents | undefined; + private _breaks: ScvdBreaks | undefined; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdComponentViewer'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + const componentViewer = getObjectFromJson(xml.component_viewer); + if ( componentViewer === undefined) { + return false; + } + + const componentIdentifier = getObjectFromJson(componentViewer.component); + if (componentIdentifier !== undefined) { + this._componentIdentifier = new ScvdComponentIdentifier(this); + this._componentIdentifier.readXml(componentIdentifier); + } + + const objectsContainer = getObjectFromJson(componentViewer.objects); + if (objectsContainer !== undefined) { + this._objects = new ScvdObjects(this); + this._objects.readXml(objectsContainer); + } + + const typedefsContainer = getObjectFromJson(componentViewer.typedefs); + if (typedefsContainer !== undefined) { + this._typedefs = new ScvdTypedefs(this); + this._typedefs.readXml(typedefsContainer); + } + + // disable for now + /*const events = getArrayFromJson(componentViewer?.events); + if (events !== undefined) { + this._events = new ScvdEvents(this); + this._events.readXml(events); + }*/ + + const allBreaks = this.collectBreakStatements(componentViewer); + if (allBreaks.length > 0) { + this._breaks = new ScvdBreaks(this); + this._breaks.readXml({ break: allBreaks }); + } + + return super.readXml(xml); + } + + public override getSymbol(_name: string): ScvdNode | undefined { + return undefined; + } + + public get component(): ScvdComponentIdentifier | undefined { + return this._componentIdentifier; + } + public get typedefs(): ScvdTypedefs | undefined { + return this._typedefs; + } + public get objects(): ScvdObjects | undefined { + return this._objects; + } + public get events(): ScvdEvents | undefined { + return this._events; + } + public get breaks(): ScvdBreaks | undefined { + return this._breaks; + } + + public configureAll(): boolean { + const ok = this.configureRecursive(this); + this.clearSymbolCachesRecursive(); + return ok; + } + private configureRecursive(item: ScvdNode): boolean { + item.configure(); + item.children.forEach(child => this.configureRecursive(child)); + return true; + } + + public async calculateTypedefs(): Promise { + const typedefs = this.typedefs; + if (typedefs === undefined || typedefs.typedef.length === 0) { + return false; + } + await typedefs.calculateTypedefs(); + return true; + } + + public validateAll(prevResult: boolean): boolean { + this.valid = prevResult; + return this.validateRecursive(this, prevResult); + } + private validateRecursive(item: ScvdNode, prevResult: boolean): boolean { + const valid = item.validate(prevResult); + item.children.forEach(child => this.validateRecursive(child, valid)); + return valid; + } + + public setExecutionContextAll(executionContext: ExecutionContext) { + this.setExecutionContextRecursive(this, executionContext); + } + private setExecutionContextRecursive(item: ScvdNode, executionContext: ExecutionContext) { + item.setExecutionContext(executionContext); + item.children.forEach(child => this.setExecutionContextRecursive(child, executionContext)); + } + + private collectBreakStatements(node: Json | Json[] | undefined): Json[] { + if (node === undefined) { + return []; + } + + if (Array.isArray(node)) { + const breaks: Json[] = []; + node.forEach(item => breaks.push(...this.collectBreakStatements(item))); + return breaks; + } + + if (typeof node !== 'object' || node === null) { + return []; + } + + const breaks: Json[] = []; + const directBreaks = getArrayFromJson(node.break); + if (directBreaks !== undefined) { + breaks.push(...directBreaks); + } + + for (const [key, value] of Object.entries(node)) { + if (key === 'break') { + continue; // already processed above + } + breaks.push(...this.collectBreakStatements(value as Json | Json[] | undefined)); + } + + return breaks; + } +} diff --git a/src/views/component-viewer/model/scvd-component.ts b/src/views/component-viewer/model/scvd-component.ts new file mode 100644 index 00000000..06c7eeaa --- /dev/null +++ b/src/views/component-viewer/model/scvd-component.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_events.html + +import { NumberType, NumberTypeInput } from './number-type'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdEventState } from './scvd-event-state'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; + +export class ScvdComponent extends ScvdNode { + private _brief: string | undefined; + private _no: number | undefined; + private _prefix: string | undefined; // hyperlink + private _state: ScvdEventState[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdComponent'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.brief = getStringFromJson(xml.brief); + this.no = getStringFromJson(xml.no); + this.prefix = getStringFromJson(xml.prefix); + + const states = getArrayFromJson(xml.state); + states?.forEach( (v: Json) => { + const newState = this.addState(); + newState.readXml(v); + }); + + return super.readXml(xml); + } + + public set brief(value: string | undefined) { + this._brief = value; + } + + public get brief(): string | undefined { + return this._brief; + } + + public set no(value: NumberTypeInput | undefined) { + if ( value === undefined) { + return; + } + this._no = new NumberType(value).value; + } + + public get no(): number | undefined { + return this._no; + } + + public set prefix(value: string | undefined) { + this._prefix = value; + } + + public get prefix(): string | undefined { + return this._prefix; + } + + public get state(): ScvdEventState[] { + return this._state; + } + public addState(): ScvdEventState { + const newState = new ScvdEventState(this); + this._state.push(newState); + return newState; + } + +} diff --git a/src/views/component-viewer/model/scvd-condition.ts b/src/views/component-viewer/model/scvd-condition.ts new file mode 100644 index 00000000..f36560bc --- /dev/null +++ b/src/views/component-viewer/model/scvd-condition.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdNode } from './scvd-node'; +import { ScvdExpression } from './scvd-expression'; +import { ExecutionContext } from '../scvd-eval-context'; + +export class ScvdCondition extends ScvdNode { + private _expression: ScvdExpression | undefined; + + constructor( + parent: ScvdNode | undefined, + expression?: string + ) { + super(parent); + if (expression !== undefined) { + this._expression = new ScvdExpression(this, expression, 'expression'); + } + } + + public override get classname(): string { + return 'ScvdCondition'; + } + + public get expression(): ScvdExpression | undefined { + return this._expression; + } + + public set expression(value: string) { + if ( this._expression === undefined) { + this._expression = new ScvdExpression(this, value, 'expression'); + return; + } + this._expression.expression = value; + } + + public async getResult(): Promise { + if (!this._expression) { + return true; + } + try { + const value = await this._expression.getValue(); + // Treat numeric zero as false; everything else (including bigint) as true. + return value === undefined ? false : value !== 0 && value !== 0n; + } catch (err) { + console.error(this.getLineInfoStr(), 'Failed to evaluate condition expression', err); + return false; + } + } + + public override setExecutionContext(executionContext: ExecutionContext): void { + super.setExecutionContext(executionContext); + this._expression?.setExecutionContext(executionContext); + } + +} diff --git a/src/views/component-viewer/model/scvd-data-type.ts b/src/views/component-viewer/model/scvd-data-type.ts new file mode 100644 index 00000000..44b5c54e --- /dev/null +++ b/src/views/component-viewer/model/scvd-data-type.ts @@ -0,0 +1,239 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ResolveSymbolCb, ResolveType } from '../resolver'; +import { ScvdNode } from './scvd-node'; +import { ScvdTypedef } from './scvd-typedef'; + +// https://arm-software.github.io/CMSIS-View/main/data_type.html#scalar_data_type + +// The following scalar data types are supported and can be used in read, typedef, and var elements. +// read and var also support arrays based on scalar data types. +const ScvdScalarDataTypeMap: Record = { + 'uint8_t': [8, 'unsigned char'], + 'int8_t': [8, 'signed char'], + 'uint16_t': [16, 'unsigned short'], + 'int16_t': [16, 'signed short'], + 'uint32_t': [32, 'unsigned int'], + 'int32_t': [32, 'signed int'], + 'uint64_t': [64, 'unsigned long long'], + 'int64_t': [64, 'signed long long'], + 'float': [32, 'single precision floating number'], + 'double': [64, 'double precision floating number'], +}; + +export class ScvdDataType extends ScvdNode { + private _type: ScvdScalarDataType | ScvdComplexDataType | undefined; + + constructor( + parent: ScvdNode | undefined, + type: string | undefined + ) { + super(parent); + this.type = type; + } + + public override get classname(): string { + return 'ScvdDataType'; + } + + public get type(): ScvdScalarDataType | ScvdComplexDataType | undefined { + return this._type; + } + + public set type(type: string | undefined) { + if (typeof type === 'string') { + const typeStr = type.replace(/\*/g, '').trim(); + Object.keys(ScvdScalarDataTypeMap).forEach(element => { // test, then create object + if (element === typeStr) { + this._type = new ScvdScalarDataType(this, type); + } + }); + if ( this._type === undefined) { // not a scalar type, create complex type + this._type = new ScvdComplexDataType(this, type); + } + } + } + + public override getMember(property: string): ScvdNode | undefined { + return this._type?.getMember(property); + } + + public override getTypeSize(): number | undefined { + const size = this._type?.getTypeSize(); + return size; + } + + public override getIsPointer(): boolean { + return this._type?.getIsPointer() ?? false; + } + + public override getVirtualSize(): number | undefined { + return this._type?.getVirtualSize(); + } + + public override getValueType(): string | undefined { + const isPointer = this.getIsPointer(); + if (isPointer) { + return 'uint32_t'; + } + + const type = this._type; + if (type !== undefined) { + const scalarType = type.castToDerived(ScvdScalarDataType); + const typeStr = scalarType?.type; + if (typeStr !== undefined) { + return typeStr; + } + } + return undefined; + } +} + + +export class ScvdScalarDataType extends ScvdNode { + private _type: string | undefined; + private _isPointer: boolean = false; + + constructor( + parent: ScvdNode | undefined, + type: string | undefined + ) { + super(parent); + if (typeof type === 'string') { + const isPointer = type.indexOf('*') === 0; + if (isPointer) { + this.isPointer = true; + } + + const typeStr = type.replace(/\*/g, '').trim(); + Object.keys(ScvdScalarDataTypeMap).forEach(element => { + if (element === typeStr) { + this._type = element; + } + }); + } + } + + public override get classname(): string { + return 'ScvdScalarDataType'; + } + + public get isPointer(): boolean { + return this._isPointer; + } + private set isPointer(value: boolean) { + this._isPointer = value; + } + + + public override getTypeSize(): number | undefined { + const info = this.type && ScvdScalarDataTypeMap[this.type]; + const value = info ? info[0]: undefined; + return value ? value / 8 : undefined; + } + + public override getIsPointer(): boolean { + return this.isPointer; + } + + public override getVirtualSize(): number | undefined { + return this.getTypeSize(); + } + + public get type(): string | undefined { + return this._type; + } + + + +} + +export class ScvdComplexDataType extends ScvdNode{ + private _typeName: string | undefined; + private _type: ScvdTypedef | undefined; + private _isPointer: boolean = false; + + constructor( + parent: ScvdNode | undefined, + typeName: string | undefined + ) { + super(parent); + this.typeName = typeName; + } + + public override get classname(): string { + return 'ScvdComplexDataType'; + } + + private set typeName(value: string | undefined) { + this._typeName = value; + } + + public get typeName(): string | undefined { + return this._typeName; + } + + public get isPointer(): boolean { + return this._isPointer; + } + private set isPointer(value: boolean) { + this._isPointer = value; + } + + public override getTypeSize(): number | undefined { + const sizeInBytes = this._type?.getTypeSize(); + if (sizeInBytes !== undefined) { + return sizeInBytes; + } + return undefined; + } + + public override getIsPointer(): boolean { + return this.isPointer; + } + + public override getVirtualSize(): number | undefined { + return this._type?.getVirtualSize(); + } + + public override resolveAndLink(resolveFunc: ResolveSymbolCb): boolean { + const typeName = this.typeName?.replace(/\*/g, '').trim(); + if (typeName === undefined) { + return false; + } + const isPointer = (this.typeName?.indexOf('*') === 0); + if (isPointer) { + this.isPointer = true; + } + + const item = resolveFunc(typeName, ResolveType.localType); + if (item === undefined || !(item instanceof ScvdTypedef)) { + console.error('Failed to resolve complex data type:', typeName); + return false; + } + this._type = item; + return true; + } + + public override getMember(property: string): ScvdNode | undefined { + return this._type?.getMember(property); + } + + + + +} diff --git a/src/views/component-viewer/model/scvd-endian.ts b/src/views/component-viewer/model/scvd-endian.ts new file mode 100644 index 00000000..4e457918 --- /dev/null +++ b/src/views/component-viewer/model/scvd-endian.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdNode } from './scvd-node'; + +export class ScvdEndian extends ScvdNode { + private _endian: string; + private readonly endianValues = ['L', 'B']; // L for little-endian, B for big-endian + private _isBigEndian: boolean = false; + + constructor( + parent: ScvdNode | undefined, + endian: string = 'L' // default is little-endian + ) { + super(parent); + this._endian = endian; + this._isBigEndian = this.endianValues.includes(endian) && endian === 'B'; + } + + public override get classname(): string { + return 'ScvdEndian'; + } + + public set endian(value: string) { + this._endian = value; + this._isBigEndian = this.endianValues.includes(value) && value === 'B'; + this.isModified = true; + } + public get endian(): string { + return this._endian; + } + + public get isBigEndian(): boolean { + return this._isBigEndian; + } + + public convertToBigEndian(value: number): number { + if (this._isBigEndian) { + // Convert to big-endian representation + return parseInt(value.toString(16).match(/.{1,2}/g)?.reverse().join('') || '0', 16); + } + return value; // No conversion needed for little-endian + } + +} diff --git a/src/views/component-viewer/model/scvd-enum.ts b/src/views/component-viewer/model/scvd-enum.ts new file mode 100644 index 00000000..4e52b7e9 --- /dev/null +++ b/src/views/component-viewer/model/scvd-enum.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_member.html#elem_enum + +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { getStringFromJson } from './scvd-utils'; + +export class ScvdEnum extends ScvdNode { + private _value: ScvdExpression; + + constructor( + parent: ScvdNode | undefined, + lastEnum: ScvdEnum | undefined, + ) { + super(parent); + const lastValue = lastEnum?.value; + const valStr = lastValue ? `(${lastValue.expression}) + 1` : '0'; + this._value = new ScvdExpression(this, valStr, 'value'); + } + + public override get classname(): string { + return 'ScvdEnum'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.value = getStringFromJson(xml.value); + this.value.readXml(xml.value as Json); + + return super.readXml(xml); + } + + public get value(): ScvdExpression { + return this._value; + } + public set value(value: string | undefined) { + if (value !== undefined) { + this._value = new ScvdExpression(this, value, 'value'); + } + } + + +} diff --git a/src/views/component-viewer/model/scvd-event-id.ts b/src/views/component-viewer/model/scvd-event-id.ts new file mode 100644 index 00000000..12370398 --- /dev/null +++ b/src/views/component-viewer/model/scvd-event-id.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdNode } from './scvd-node'; +import { ScvdExpression } from './scvd-expression'; + +export class ScvdEventId extends ScvdNode { + private _id: ScvdExpression; + private _messageNumber: number | undefined; + private _componentNumber: number | undefined; + private _level: number | undefined; + + constructor( + parent: ScvdNode | undefined, + id: string, + ) { + super(parent); + this._id = new ScvdExpression(this, id, 'id'); + } + + public override get classname(): string { + return 'ScvdEventId'; + } + + public get id(): ScvdExpression { + return this._id; + } + + public get messageNumber(): number | undefined { + return this._messageNumber; + } + + public get componentNumber(): number | undefined { + return this._componentNumber; + } + + public get level(): number | undefined { + return this._level; + } + + public override configure(): boolean { + const id = this._id; + if (id !== undefined ) { + id.configure(); + const constValue = id.expressionAst?.constValue; + if (typeof constValue === 'number') { + this._messageNumber = constValue & 0xFF; + this._componentNumber = (constValue >> 8) & 0xFF; + this._level = (constValue >> 16) & 0x3; + } + } + + return super.configure(); + } + + public override validate(prevResult: boolean): boolean { + return super.validate(prevResult && true); + } + + + +} diff --git a/src/views/component-viewer/model/scvd-event-level.ts b/src/views/component-viewer/model/scvd-event-level.ts new file mode 100644 index 00000000..90da9d8b --- /dev/null +++ b/src/views/component-viewer/model/scvd-event-level.ts @@ -0,0 +1,76 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdNode } from './scvd-node'; + +export enum EventLevel { + EventLevelError = 0, // Run-time error + EventLevelAPI = 1, // API function call + EventLevelOp = 2, // Internal operation + EventLevelDetail = 3, // Additional detailed information of operations +} + +export const EventLevelMap: Map = new Map([ + ['Error', EventLevel.EventLevelError], + ['API', EventLevel.EventLevelAPI], + ['Op', EventLevel.EventLevelOp], + ['Detail', EventLevel.EventLevelDetail], +]); + +export const EventLevelReverseMap: Map = new Map([ + [EventLevel.EventLevelError, 'Error'], + [EventLevel.EventLevelAPI, 'API'], + [EventLevel.EventLevelOp, 'Op'], + [EventLevel.EventLevelDetail, 'Detail'], +]); + +export class ScvdEventLevel extends ScvdNode { + private _level: EventLevel | undefined; + + constructor( + parent: ScvdNode | undefined, + level: string, + ) { + super(parent); + this.level = level; + } + + public override get classname(): string { + return 'ScvdEventLevel'; + } + + public get level(): EventLevel | undefined { + return this._level; + } + + public set level(value: string | undefined) { + const level = EventLevelMap.get(value ?? ''); + if ( level !== undefined ) { + this._level = level; + } else { + this._level = EventLevel[value as keyof typeof EventLevel]; + } + } + + public filterLevel(level: EventLevel): boolean { + return this._level !== undefined ? this._level <= level : true; + } + + + +} diff --git a/src/views/component-viewer/model/scvd-event-state.ts b/src/views/component-viewer/model/scvd-event-state.ts new file mode 100644 index 00000000..6b4cfaa3 --- /dev/null +++ b/src/views/component-viewer/model/scvd-event-state.ts @@ -0,0 +1,111 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { getStringFromJson } from './scvd-utils'; + +const EVENT_COLORS = ['blue', 'red', 'green', 'black'] as const; +type EventColor = (typeof EVENT_COLORS)[number]; +function isEventColor(v: string): v is EventColor { + return EVENT_COLORS.includes(v as EventColor); +} + +export class ScvdEventState extends ScvdNode { + private _plot: 'off' | 'line' | 'box' = 'off'; + private _color: EventColor = 'blue'; + private _unique: boolean = false; + private _dormant: boolean = false; + private _ssel: boolean = false; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdEventState'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.plot = getStringFromJson(xml.plot); + this.color = getStringFromJson(xml.color); + this.unique = getStringFromJson(xml.unique); + this.dormant = getStringFromJson(xml.dormant); + this.ssel = getStringFromJson(xml.ssel); + + return super.readXml(xml); + } + + public get plot(): 'off' | 'line' | 'box' { + return this._plot; + } + + public set plot(value: string | undefined) { + if (value !== undefined && (value === 'off' || value === 'line' || value === 'box')) { + this._plot = value; + } + } + + public get color(): EventColor { + return this._color; + } + + public set color(value: string | undefined) { + if (value !== undefined && isEventColor(value)) { + this._color = value; + } + } + + public get unique(): boolean { + return this._unique; + } + + public set unique(value: string | boolean | undefined) { + if (value !== undefined) { + this._unique = (value === 'true' || value === true); + } + } + + public get dormant(): boolean { + return this._dormant; + } + + public set dormant(value: string | boolean | undefined) { + if (value !== undefined) { + this._dormant = (value === 'true' || value === true); + } + } + + public get ssel(): boolean { + return this._ssel; + } + + public set ssel(value: string | boolean | undefined) { + if (value !== undefined) { + this._ssel = (value === 'true' || value === true); + } + } + +} diff --git a/src/views/component-viewer/model/scvd-event-tracking.ts b/src/views/component-viewer/model/scvd-event-tracking.ts new file mode 100644 index 00000000..8a7fcae3 --- /dev/null +++ b/src/views/component-viewer/model/scvd-event-tracking.ts @@ -0,0 +1,58 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdNode } from './scvd-node'; + +/** + * Specifies the tracking for an event handle. + * - Start: start the state tracking for an event handle. + * - Stop: stop the state tracking for an event handle. + * - Reset: initialize the tracking for all event handles of that component to "Stop". + */ +export enum ScvdEventTrackingMode { + Start = 'Start', + Stop = 'Stop', + Reset = 'Reset', +} + +export class ScvdEventTracking extends ScvdNode { + private _mode: ScvdEventTrackingMode | undefined; + + constructor( + parent: ScvdNode | undefined, + mode: string, + ) { + super(parent); + this.mode = mode; + } + + public override get classname(): string { + return 'ScvdEventTracking'; + } + + public get mode(): ScvdEventTrackingMode | undefined { + return this._mode; + } + + public set mode(value: string | undefined) { + if (value !== undefined) { + this._mode = ScvdEventTrackingMode[value as keyof typeof ScvdEventTrackingMode]; + } + } + +} diff --git a/src/views/component-viewer/model/scvd-event.ts b/src/views/component-viewer/model/scvd-event.ts new file mode 100644 index 00000000..4cf72788 --- /dev/null +++ b/src/views/component-viewer/model/scvd-event.ts @@ -0,0 +1,194 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ResolveSymbolCb } from '../resolver'; +import { NumberType, NumberTypeInput } from './number-type'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdEventId } from './scvd-event-id'; +import { ScvdEventLevel } from './scvd-event-level'; +import { ScvdEventState } from './scvd-event-state'; +import { ScvdEventTracking } from './scvd-event-tracking'; +import { ScvdExpression } from './scvd-expression'; +import { ScvdPrint } from './scvd-print'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; +import { ScvdValueOutput } from './scvd-value-output'; + +export class ScvdEvent extends ScvdNode { + private _id: ScvdEventId | undefined; + private _level: ScvdEventLevel | undefined; + private _property: ScvdValueOutput | undefined; + private _value: ScvdValueOutput | undefined; + private _doc: string | undefined; + private _handle: number | undefined; + private _hname: ScvdExpression | undefined; + private _stateName : string | undefined; // name of referenced state + private _state: ScvdEventState | undefined; // reference + private _tracking: ScvdEventTracking | undefined; + private _print: ScvdPrint[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdEvent'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.id = getStringFromJson(xml.id); + this.level = getStringFromJson(xml.level); + this.property = getStringFromJson(xml.property); + this.value = getStringFromJson(xml.value); + this.doc = getStringFromJson(xml.doc); + this.handle = getStringFromJson(xml.handle); + this.hname = getStringFromJson(xml.hname); + this.stateName = getStringFromJson(xml.state); + this.tracking = getStringFromJson(xml.tracking); + + const print = getArrayFromJson(xml.print); + print?.forEach( (v: Json) => { + const printItem = this.addPrint(); + printItem.readXml(v); + }); + + return super.readXml(xml); + } + + public override resolveAndLink(_resolveFunc: ResolveSymbolCb): boolean { + // TOIMPL: this._state = this.findReference(ScvdEventState, this._state?.name); + return false; + } + + public get id(): ScvdEventId | undefined { + return this._id; + } + public set id(value: string | undefined) { + if ( value !== undefined ) { + this._id = new ScvdEventId(this, value); + } + } + + public get level(): ScvdEventLevel | undefined { + return this._level; + } + public set level(value: string | undefined) { + if ( value !== undefined ) { + if ( this._level === undefined ) { + this._level = new ScvdEventLevel(this, value); + return; + } + this._level.level = value; + } + } + + public get property(): ScvdValueOutput | undefined { + return this._property; + } + public set property(value: string | undefined) { + if ( value !== undefined ) { + if ( this._property === undefined ) { + this._property = new ScvdValueOutput(this, value, 'property'); + return; + } + this._property.expression = value; + } + } + + public get value(): ScvdValueOutput | undefined { + return this._value; + } + public set value(value: string | undefined) { + if ( value !== undefined ) { + this._value = new ScvdValueOutput(this, value, 'value'); + } + } + + public get doc(): string | undefined { + return this._doc; + } + public set doc(value: string | undefined) { + this._doc = value; + } + + public get handle(): number | undefined { + return this._handle; + } + public set handle(value: NumberTypeInput | undefined) { + if ( value !== undefined ) { + this._handle = new NumberType(value).value; + } + } + + public get hname(): ScvdExpression | undefined { + return this._hname; + } + public set hname(value: string | undefined) { + if ( value !== undefined ) { + this._hname = new ScvdExpression(this, value, 'hname'); + } + } + + public get state(): ScvdEventState | undefined { + return this._state; + } + + // TOIMPL, resolve and link + public set state(value: ScvdEventState | undefined) { + this._state = value; + } + + public get tracking(): ScvdEventTracking | undefined { + return this._tracking; + } + public set tracking(value: string | undefined) { + if ( value !== undefined ) { + if ( this._tracking === undefined ) { + this._tracking = new ScvdEventTracking(this, value); + return; + } + this._tracking.mode = value; + } + } + + public get print(): ScvdPrint[] { + return this._print; + } + + public addPrint(): ScvdPrint { + const item = new ScvdPrint(this); + this._print.push(item); + return item; + } + + public get stateName(): string | undefined { + return this._stateName; + } + public set stateName(value: string | undefined) { + this._stateName = value; + } + + + +} diff --git a/src/views/component-viewer/model/scvd-events.ts b/src/views/component-viewer/model/scvd-events.ts new file mode 100644 index 00000000..9aaab54e --- /dev/null +++ b/src/views/component-viewer/model/scvd-events.ts @@ -0,0 +1,81 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdEvent } from './scvd-event'; +import { ScvdGroup } from './scvd-group'; +import { getArrayFromJson } from './scvd-utils'; + +export class ScvdEvents extends ScvdNode { + private _event: ScvdEvent[] = []; + private _group: ScvdGroup[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdEvents'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + const events = getArrayFromJson(xml); + events?.forEach( (v: Json) => { + const event = getArrayFromJson(v.event); + event?.forEach( (v: Json) => { + const item = this.addEvent(); + item.readXml(v); + }); + + const groups = getArrayFromJson(v.group); + groups?.forEach( (v: Json) => { + const item = this.addGroup(); + item.readXml(v); + }); + }); + + return super.readXml(xml); + } + + public get event(): ScvdEvent[] { + return this._event; + } + + public addEvent(): ScvdEvent { + const event = new ScvdEvent(this); + this._event.push(event); + return event; + } + + public get group(): ScvdGroup[] { + return this._group; + } + public addGroup(): ScvdGroup { + const group = new ScvdGroup(this); + this._group.push(group); + return group; + } + +} diff --git a/src/views/component-viewer/model/scvd-expression.ts b/src/views/component-viewer/model/scvd-expression.ts new file mode 100644 index 00000000..b8a5a07a --- /dev/null +++ b/src/views/component-viewer/model/scvd-expression.ts @@ -0,0 +1,162 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/scvd_expression.html + + +import { parseExpression, ParseResult } from '../parser-evaluator/parser'; +import { evaluateParseResult, EvaluateResult } from '../parser-evaluator/evaluator'; +import { ScvdNode } from './scvd-node'; +import { ExecutionContext } from '../scvd-eval-context'; + +export class ScvdExpression extends ScvdNode { + private _expression: string | undefined; + private _scvdVarName: string | undefined; + private _expressionAst: ParseResult | undefined; + private _isPrintExpression: boolean = false; + private _executionContext: ExecutionContext | undefined; + + constructor( + parent: ScvdNode | undefined, + expression: string | undefined, + scvdVarName: string, + isPrintExpression?: boolean, + ) { + super(parent); + this.expression = expression; + this.scvdVarName = scvdVarName; + this.tag = scvdVarName; + this._isPrintExpression = isPrintExpression ?? false; + } + + public override get classname(): string { + return 'ScvdExpression'; + } + + public get expressionAst(): ParseResult | undefined { + return this._expressionAst; + } + public set expressionAst(ast: ParseResult | undefined) { + this._expressionAst = ast; + } + + public get isPrintExpression(): boolean { + return this._isPrintExpression; + } + + public get expression(): string | undefined { + return this._expression; + } + public set expression(expression: string | undefined) { + this._expression = expression; + this._expressionAst = undefined; + } + + private async evaluateExpression(): Promise { + if (this.expressionAst === undefined || this._executionContext === undefined) { + console.error(this.getLineInfoStr(), 'Expression evaluation missing AST or execution context'); + return undefined; + } + return evaluateParseResult(this.expressionAst, this._executionContext.evalContext); + } + + public override async getValue(): Promise { + return this.evaluate(); + } + + public get scvdVarName(): string | undefined { + return this._scvdVarName; + } + public set scvdVarName(value: string | undefined) { + this._scvdVarName = value; + } + + public async evaluate(): Promise { + if (this.expressionAst === undefined) { + return undefined; + } + if (this.expressionAst.constValue === undefined) { // not a constant expression + const result = await this.evaluateExpression(); + return result; + } else { // constant expression + const constVal = this.expressionAst.constValue; + if (typeof constVal === 'boolean') { + return constVal ? 1 : 0; + } + return constVal; + } + } + + private parseExpression(): boolean { + const expression = this.expression; + if (expression === undefined) { + this.expressionAst = undefined; + return false; + } + + if (this.expressionAst === undefined) { // if already parsed by dependency, skip parsing + const expressionAst = parseExpression(expression, this.isPrintExpression); + if (expressionAst !== undefined && expressionAst.diagnostics.length === 0) { + this.expressionAst = expressionAst; + } + } + + return true; + } + + public override configure(): boolean { + this.parseExpression(); + return super.configure(); + } + + public override setExecutionContext(_executionContext: ExecutionContext) { + this._executionContext = _executionContext; + } + + public override validate(prevResult: boolean): boolean { + const expression = this.expression; + if (expression === undefined) { + console.error(this.getLineInfoStr(), 'Expression is empty.'); + return super.validate(false); + } + + const expressionAst = this.expressionAst; + if (expressionAst === undefined) { + console.error(this.getLineInfoStr(), 'Expression AST is undefined for expression: ', expression); + return super.validate(false); + } + if (expressionAst.diagnostics.length > 0) { + console.error(this.getLineInfoStr(), 'Expression AST has diagnostics for expression: ', expression, '\nDiagnostics: ', expressionAst.diagnostics); + return super.validate(false); + } + + return super.validate(prevResult && true); + } + + public async getResultString(): Promise { + const value = await this.evaluate(); + if (typeof value === 'number') { + return value.toString(); + } + if (typeof value === 'string') { + return value; + } + if (typeof value === 'bigint') { + return value.toString(); + } + return undefined; + } +} diff --git a/src/views/component-viewer/model/scvd-format-specifier.ts b/src/views/component-viewer/model/scvd-format-specifier.ts new file mode 100755 index 00000000..0b8f5624 --- /dev/null +++ b/src/views/component-viewer/model/scvd-format-specifier.ts @@ -0,0 +1,713 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + + +import { NumberType, NumFormat } from './number-type'; + +export type FormatKind = 'int' | 'uint' | 'float' | 'unknown'; +export interface FormatTypeInfo { + kind: FormatKind; + bits?: number; +} + +export interface FormatOptions { + typeInfo?: FormatTypeInfo; + enumText?: string; + allowUnknownSpec?: boolean; + padHex?: boolean; +} + +export class ScvdFormatSpecifier { + private utf16leDecoder: TextDecoder; + + constructor( + ) { + this.utf16leDecoder = new TextDecoder('utf-16le'); + } + + public formatNumberByType(value: number | bigint, opts: { kind: 'int' | 'uint' | 'float' | 'unknown'; bits?: number }): string { + const { kind, bits } = opts; + // normalize number inputs for integer-like kinds + if ((kind === 'int' || kind === 'uint') && typeof value === 'number') { + value = Math.trunc(value); + } + const asBig = (v: number | bigint): bigint | undefined => { + if (typeof v === 'bigint') { + return v; + } + if (!Number.isFinite(v)) { + return undefined; + } + return BigInt(Math.trunc(v)); + }; + + const preferBig = kind !== 'float' && (typeof value === 'bigint' || (!!bits && bits > 32)); + if (preferBig) { + const vb = asBig(value); + if (vb === undefined) { + return `${value}`; + } + if (kind === 'uint') { + if (bits && bits > 0) { + const mask = (1n << BigInt(bits)) - 1n; + return (vb & mask).toString(10); + } + return vb.toString(10); + } + if (kind === 'int') { + if (bits && bits > 0) { + const mask = (1n << BigInt(bits)) - 1n; + let m = vb & mask; + const sign = 1n << BigInt(bits - 1); + if ((m & sign) !== 0n) { + m -= (1n << BigInt(bits)); + } + return m.toString(10); + } + return vb.toString(10); + } + return vb.toString(10); + } + + if (!Number.isFinite(value as number)) { + return `${value}`; + } + + if (kind === 'float') { + // Keep floats readable but short; mirror legacy behaviour (float ~3 decimals, double ~6+). + if (bits && bits <= 32) { + return (value as number).toFixed(3); + } + return (value as number).toPrecision(6); + } + + if (kind === 'int') { + if (bits && bits < 32) { + const shift = 32 - bits; + const signed = ((value as number) << shift) >> shift; + return signed.toString(10); + } + return ((value as number) | 0).toString(10); + } + + if (kind === 'uint') { + if (bits && bits < 32) { + const mask = (1 << bits) >>> 0; + return (((value as number) >>> 0) & mask).toString(10); + } + return ((value as number) >>> 0).toString(10); + } + + return (value as number).toString(10); + } + + /** + * Central formatter that applies printf-like specifiers to the already-resolved value. + * All data fetching and symbol/memory lookups should happen before calling this. + */ + public format(spec: string, value: unknown, options?: FormatOptions): string { + const typeInfo = options?.typeInfo; + const enumText = options?.enumText; + const padHex = options?.padHex ?? false; + + const toNumeric = (v: unknown): number | bigint => { + if (typeof v === 'number' || typeof v === 'bigint') { + return v; + } + if (typeof v === 'boolean') { + return v ? 1 : 0; + } + if (typeof v === 'string') { + const n = Number(v); + return Number.isFinite(n) ? n : NaN; + } + return NaN; + }; + + const toNumber = toNumeric; + + const numOpts = (kind: FormatKind) => { + const o: { kind: FormatKind; bits?: number } = { kind }; + if (typeInfo?.bits !== undefined) { + o.bits = typeInfo.bits; + } + return o; + }; + + switch (spec) { + case 'd': { + let n = toNumber(value); + if (typeof n === 'number') { + n = Math.trunc(n); + } + return this.formatNumberByType(n, numOpts('int')); + } + case 'u': { + let n = toNumber(value); + if (typeof n === 'number') { + n = Math.trunc(n); + } + return this.formatNumberByType(n, numOpts('uint')); + } + case 'x': { + let n = toNumeric(value); + if (typeof n === 'number' && (typeInfo?.kind ?? 'unknown') !== 'float') { + n = Math.trunc(n); + } + return this.format_x(n, typeInfo, padHex); + } + case 't': { + if (typeof value === 'string') { + const sanitized = this.sanitizeLiteral(value); + if (!sanitized) { + return 'bad literal - %t: text with embedded %format specifier(s)'; + } + return this.escapeNonPrintable(sanitized); + } + if (value instanceof Uint8Array) { + return this.escapeNonPrintable(this.format_N(value)); + } + return String(value); + } + case 'C': { + return this.format_C(value as number | string); + } + case 'S': { + return this.format_S(value as number | string); + } + case 'E': { + if (enumText) { + return this.format_E(enumText); + } + const n = toNumber(value); + return this.format_E(Number.isFinite(n) ? this.formatNumberByType(n, numOpts('int')) : String(value)); + } + case 'I': { + if (value instanceof Uint8Array) { + return this.formatIpv4(value); + } + const n = toNumber(value); + return this.format_I(n as number | bigint); + } + case 'J': { + if (value instanceof Uint8Array) { + return this.formatIpv6(value); + } + return this.format_J(value as number | string | bigint); + } + case 'N': { + if (value instanceof Uint8Array) { + return this.format_N(value); + } + return String(value); + } + case 'M': { + if (value instanceof Uint8Array) { + return this.formatMac(value); + } + return this.format_M(value as number | string); + } + case 'T': { + return this.format_T(value, typeInfo, padHex); + } + case 'U': { + if (value instanceof Uint8Array) { + return this.format_U(value); + } + return String(value); + } + case '%': { + return this.format_percent(); + } + default: { + if (options?.allowUnknownSpec) { + return ``; + } + return String(value); + } + } + } + + public formatHex(value: number | bigint, bitsOrTypeInfo?: number | FormatTypeInfo, padZeroes: boolean = false): string { + const bits = typeof bitsOrTypeInfo === 'number' ? bitsOrTypeInfo : bitsOrTypeInfo?.bits; + + if (typeof value === 'bigint') { + const widthRaw = bits ? Math.ceil(bits / 4) : 0; + const width = padZeroes && widthRaw > 0 ? Math.min(widthRaw, 16) : 0; + const hex = value.toString(16); + const padded = width > 0 ? hex.padStart(width, '0') : hex; + return '0x' + padded; + } + const n = Number(value); + if (!Number.isFinite(n)) { + return `${value}`; + } + const widthRaw = bits ? Math.ceil(bits / 4) : 0; + const width = padZeroes && widthRaw > 0 ? Math.min(widthRaw, 16) : 0; // cap padding to 64-bit to avoid runaway zeros + const hex = (n >>> 0).toString(16); + const padded = width > 0 ? hex.padStart(width, '0') : hex; + return '0x' + padded; + } + + public format_d(value: number | string): string { + const n = Number(value); + return Number.isFinite(n) ? n.toString(10) : `${value}`; + } + + public format_u(value: number | string): string { + const n = Number(value); + if (!Number.isFinite(n)) { + return `${value}`; + } + return (n >>> 0).toString(10); + } + + public format_t(value: number | string | Uint8Array): string { + // Already a string: nothing to do + if (typeof value === 'string') { + return value; + } + + // Number: whatever your existing formatting rule is + if (typeof value === 'number') { + return value.toString(); + } + + // C string: null-terminated Uint8Array + if (value instanceof Uint8Array) { + // Find first 0 byte (C '\0') + let end = value.indexOf(0); + if (end === -1) { + end = value.length; + } + + // Prefer UTF-8 decode (typical for C strings nowadays) + if (typeof TextDecoder !== 'undefined') { + const dec = new TextDecoder('utf-8', { fatal: false }); + return dec.decode(value.subarray(0, end)); + } + + // Fallback: simple byte→char (ASCII / Latin-1 style) + let s = ''; + for (const ch of value.subarray(0, end)) { + s += String.fromCharCode(ch); + } + return s; + } + + // Fallback in case something weird sneaks in + return String(value); + } + + public format_x(value: number | string | bigint, typeInfo?: FormatTypeInfo, padZeroes: boolean = false): string { + if (typeof value === 'bigint') { + return this.formatHex(value, typeInfo, padZeroes); + } + const n = Number(value); + if (!Number.isFinite(n)) { + return `${value}`; + } + return this.formatHex(n, typeInfo, padZeroes); + } + + public format_address_like(value: number | string): string { + if ( typeof value === 'string') { + return value; + } + if (typeof value === 'number') { + const num = new NumberType(value, NumFormat.hexadecimal, 32); + return num.getDisplayText(); + } + return ''; + } + + public format_C(value: number | string): string { + return this.format_address_like(value); + } + + public format_S(value: number | string): string { + return this.format_address_like(value); + } + + public format_E(value: number | string): string { + return this.format_d(value); // either string or number + } + + public format_I(value: number | string | bigint): string { + if (typeof value === 'string') { + return value; + } + const n = typeof value === 'bigint' ? Number(value) : Number(value); + if (!Number.isFinite(n)) { + return `${value}`; + } + const b0 = (n >>> 24) & 0xFF; + const b1 = (n >>> 16) & 0xFF; + const b2 = (n >>> 8) & 0xFF; + const b3 = n & 0xFF; + return `${b0}.${b1}.${b2}.${b3}`; + } + + public format_J(value: number | string | bigint): string { + // If already a string, assume formatted IPv6 + if (typeof value === 'string') { + return value; + } + // Cannot reliably format numeric IPv6 (needs 128-bit); fallback to hex + return this.format_x(value); + } + + // ASCII string + public format_N(value: Uint8Array): string { + return this.decodeAscii(value); + } + + // Unicode string + public format_U(value: Uint8Array): string { + return this.decodeWcharFromBytes(value); + } + + + public format_M(value: number | string): string { + if (typeof value === 'string') { + const cleaned = value.replace(/[^0-9a-fA-F]/g, '').slice(0, 12).padStart(12, '0'); + return cleaned.match(/.{1,2}/g)?.join('-').toUpperCase() ?? value; + } + const n = Number(value); + if (!Number.isFinite(n)) { + return `${value}`; + } + const parts: string[] = []; + for (let i = 5; i >= 0; i--) { + parts.push(((n >> (i * 8)) & 0xFF).toString(16).padStart(2, '0')); + } + return parts.join('-').toUpperCase(); + } + + public format_T(value: unknown, typeInfo?: FormatTypeInfo, padZeroes: boolean = false): string { + // Spec: Value in format derived from expression type (hexadecimal or floating number) + const asNumeric = (v: unknown): number | bigint | undefined => { + if (typeof v === 'number' || typeof v === 'bigint') { + return v; + } + if (typeof v === 'string') { + const n = Number(v); + return Number.isFinite(n) ? n : undefined; + } + return undefined; + }; + + const n = asNumeric(value); + if (n === undefined) { + return String(value); + } + + const kind: FormatKind = + typeInfo?.kind && typeInfo.kind !== 'unknown' + ? typeInfo.kind + : (typeof n === 'number' && !Number.isInteger(n) ? 'float' : 'int'); + + if (kind === 'float') { + // Preserve fractional part for floats + const v = typeof n === 'bigint' ? Number(n) : n; + const bits = typeInfo?.bits; + const opts = bits !== undefined ? { kind: 'float' as const, bits } : { kind: 'float' as const }; + return this.formatNumberByType(v, opts); + } + + // integer-like → hex with optional padding based on bits; truncate if number is fractional + const bits = typeInfo?.bits; + const paddedBits = bits && bits > 0 ? bits : undefined; + const v = typeof n === 'number' ? Math.trunc(n) : n; + return this.formatHex(v, paddedBits, padZeroes); + } + + public format_percent(): string { + return '%'; + } + + public formatIpv4(bytes: Uint8Array): string { + if (bytes.length < 4) { + return ''; + } + return `${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`; + } + + public formatIpv6(bytes: Uint8Array): string { + /* eslint-disable security/detect-object-injection */ + if (bytes.length < 16) { + return ''; + } + const words: number[] = []; + for (let i = 0; i < 8; i++) { + words.push(((bytes[i * 2] << 8) | (bytes[i * 2 + 1])) >>> 0); + } + // Collapse the longest run of zeros for compact form + const hexWords = words.map(w => w.toString(16)); + let bestStart = -1; let bestLen = 0; + let curStart = -1; let curLen = 0; + for (let i = 0; i < hexWords.length; i++) { + if (words[i] === 0) { + if (curStart === -1) { + curStart = i; curLen = 1; + } else { + curLen++; + } + } else { + if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; } + curStart = -1; curLen = 0; + } + } + if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; } + + const out: string[] = []; + for (let i = 0; i < hexWords.length; i++) { + if (bestLen >= 2 && i >= bestStart && i < bestStart + bestLen) { + if (out[out.length - 1] !== '') { + out.push(''); + } + if (i === bestStart) { + out.push(''); + } + continue; + } + out.push(hexWords[i]); + } + return out.join(':').replace(/^:/, '::').replace(/:::/, '::'); + /* eslint-enable security/detect-object-injection */ + } + + public formatMac(bytes: Uint8Array): string { + if (bytes.length < 6) { + return ''; + } + return Array.from(bytes.subarray(0, 6)).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join('-'); + } + + public sanitizeLiteral(str: string): string | undefined { + /* eslint-disable security/detect-object-injection */ + // Reject embedded unescaped '%' to match legacy %t semantics + for (let i = 0; i < str.length; i++) { + if (str[i] === '%') { + if (str[i + 1] === '%') { + i++; // skip escaped %% + continue; + } + return undefined; + } + } + return str; + /* eslint-enable security/detect-object-injection */ + } + + public escapeNonPrintable(str: string): string { + let out = ''; + for (let i = 0; i < str.length; i++) { + const ch = str.charCodeAt(i); + if (ch === 0) { + break; + } + if (ch === 0x0A) { out += '\\n'; continue; } + if (ch === 0x0D) { out += '\\r'; continue; } + if (ch === 0x09) { out += '\\t'; continue; } + if (ch === 0x0B) { out += '\\v'; continue; } + if (ch === 0x0C) { out += '\\f'; continue; } + if (ch === 0x08) { out += '\\b'; continue; } + if (ch === 0x07) { out += '\\a'; continue; } + if (ch < 0x20 || ch >= 0x7F) { + out += `\\${ch.toString(16).padStart(2, '0').toUpperCase()}`; + continue; + } + out += str.charAt(i); + } + return out; + } + + private decodeWcharFromBytes(bytes: Uint8Array): string { + // If you have a null terminator, trim at the first 0x0000 + let end = bytes.length; + for (let i = 0; i + 1 < bytes.length; i += 2) { + // eslint-disable-next-line security/detect-object-injection -- false positive: controlled typed-array indexing for UTF-16 scanning + if (bytes[i] === 0 && bytes[i + 1] === 0) { + end = i; + break; + } + } + const slice = bytes.subarray(0, end); + return this.utf16leDecoder.decode(slice); + } + + private decodeAscii(bytes: Uint8Array): string { + const chars: number[] = []; + for (const b of bytes) { + if (b === 0) { + break; + } // if you use C-style null termination + chars.push(b & 0x7F); // or just `chars.push(b);` if high bits never set + } + return String.fromCharCode(...chars); + } + + + /** + * Pretty-prints a USB descriptor as text. + * - Accepts the raw descriptor bytes (starting at bLength, bDescriptorType). + * - Supports Device(1), Config(2), String(3), Interface(4), Endpoint(5), + * Device Qualifier(6), BOS(0x0F). Unknown types are hex-dumped. + */ + public printUsbDescriptor(value: Uint8Array): string { + if (!value || value.length < 2) { + return '(invalid: too short)'; + } + const bLength = value[0] || value.length; // be forgiving if devices put 0 + const bType = value[1]; + const len = Math.min(bLength, value.length); + + const u16 = (off: number) => + off + 1 < len ? ((value.at(off) ?? 0) | ((value.at(off + 1) ?? 0) << 8)) : 0; + + const hex = (n: number, w = 2) => '0x' + n.toString(16).toUpperCase().padStart(w, '0'); + const hexdump = (arr: Uint8Array) => + Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join(' '); + + const typeMap = new Map([ + [0x01, 'Device'], + [0x02, 'Configuration'], + [0x03, 'String'], + [0x04, 'Interface'], + [0x05, 'Endpoint'], + [0x06, 'Device Qualifier'], + [0x07, 'Other Speed Config'], + [0x08, 'Interface Power'], + [0x0F, 'BOS'], + ]); + const typeName = (t: number) => typeMap.get(t) ?? `Type ${hex(t)}`; + + // --- String Descriptor (Type 0x03) --- + // bLength, bDescriptorType, then UTF-16LE code units + if (bType === 0x03) { + if (len < 2) { + return 'USB String: (invalid)'; + } + if (len === 2) { + return 'USB String: (empty)'; + } + // indices 2..len-1 contain UTF-16LE units + const codeUnitCount = Math.max(0, (len - 2) >> 1); + const units = new Uint16Array(codeUnitCount); + for (let i = 0; i < codeUnitCount; i++) { + const lo = value.at(2 + (i << 1)) ?? 0; + const hi = value.at(3 + (i << 1)) ?? 0; + units.set([lo | (hi << 8)], i); + } + // Convert UTF-16 units to JS string safely (surrogates pass through) + let out = ''; + for (let i = 0; i < units.length; i++) { + out += String.fromCharCode(units.at(i) ?? 0); + } + return `USB String: "${out}"`; + } + + // --- Device Descriptor (Type 0x01, 18 bytes) --- + if (bType === 0x01) { + return [ + `USB Device (len=${len})`, + ` bcdUSB ${hex(u16(2), 4)}`, + ` bDeviceClass ${hex(value[4])}`, + ` bDeviceSubClass ${hex(value[5])}`, + ` bDeviceProtocol ${hex(value[6])}`, + ` bMaxPacketSize0 ${value[7]}`, + ` idVendor ${hex(u16(8), 4)}`, + ` idProduct ${hex(u16(10), 4)}`, + ` bcdDevice ${hex(u16(12), 4)}`, + ` iManufacturer ${value[14]}`, + ` iProduct ${value[15]}`, + ` iSerialNumber ${value[16]}`, + ` bNumConfigurations ${value[17]}` + ].join('\n'); + } + + // --- Configuration Descriptor (Type 0x02, 9 bytes) --- + if (bType === 0x02) { + return [ + `USB Configuration (len=${len})`, + ` wTotalLength ${u16(2)} bytes`, + ` bNumInterfaces ${value[4]}`, + ` bConfigurationValue ${value[5]}`, + ` iConfiguration ${value[6]}`, + ` bmAttributes ${hex(value[7])}`, + ` bMaxPower ${value[8] * 2} mA` + ].join('\n'); + } + + // --- Interface Descriptor (Type 0x04, 9 bytes) --- + if (bType === 0x04) { + return [ + `USB Interface (len=${len})`, + ` bInterfaceNumber ${value[2]}`, + ` bAlternateSetting ${value[3]}`, + ` bNumEndpoints ${value[4]}`, + ` bInterfaceClass ${hex(value[5])}`, + ` bInterfaceSubCls ${hex(value[6])}`, + ` bInterfaceProto ${hex(value[7])}`, + ` iInterface ${value[8]}` + ].join('\n'); + } + + // --- Endpoint Descriptor (Type 0x05, 7 bytes) --- + if (bType === 0x05) { + const addr = value[2] ?? 0; + const epNum = addr & 0x0F; + const dirIn = (addr & 0x80) !== 0; + const attrs = value[3] ?? 0; + const xferType = ['Control', 'Isochronous', 'Bulk', 'Interrupt'][attrs & 0x3]; + return [ + `USB Endpoint (len=${len})`, + ` bEndpointAddress ${hex(addr)} (EP${epNum} ${dirIn ? 'IN' : 'OUT'})`, + ` bmAttributes ${hex(attrs)} (${xferType})`, + ` wMaxPacketSize ${u16(4)}`, + ` bInterval ${value[6] ?? 0}` + ].join('\n'); + } + + // --- Device Qualifier (Type 0x06, 10 bytes) --- + if (bType === 0x06) { + return [ + `USB Device Qualifier (len=${len})`, + ` bcdUSB ${hex(u16(2), 4)}`, + ` bDeviceClass ${hex(value[4])}`, + ` bDeviceSubClass ${hex(value[5])}`, + ` bDeviceProtocol ${hex(value[6])}`, + ` bMaxPacketSize0 ${value[7]}`, + ` bNumConfigurations ${value[8]}` + ].join('\n'); + } + + // --- BOS (Type 0x0F, 5 bytes) --- + if (bType === 0x0F) { + return [ + `USB BOS (len=${len})`, + ` wTotalLength ${u16(2)} bytes`, + ` bNumDeviceCaps ${value[4] ?? 0}` + ].join('\n'); + } + + // Fallback: generic dump + return `${typeName(bType)} (len=${len}): ${hexdump(value.subarray(0, len))}`; + } +} diff --git a/src/views/component-viewer/model/scvd-group.ts b/src/views/component-viewer/model/scvd-group.ts new file mode 100644 index 00000000..d5ef2997 --- /dev/null +++ b/src/views/component-viewer/model/scvd-group.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdComponent } from './scvd-component'; +import { getArrayFromJson } from './scvd-utils'; + +export class ScvdGroup extends ScvdNode { + private _component: ScvdComponent[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdGroup'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + const components = getArrayFromJson(xml.component); + components?.forEach( (component: Json) => { + const newComponent = this.addComponent(); + newComponent.readXml(component); + }); + + return super.readXml(xml); + } + + public addComponent(): ScvdComponent { + const newComponent = new ScvdComponent(this); + this._component.push(newComponent); + return newComponent; + } + + public get components(): ScvdComponent[] { + return this._component; + } + +} diff --git a/src/views/component-viewer/model/scvd-gui-interface.ts b/src/views/component-viewer/model/scvd-gui-interface.ts new file mode 100644 index 00000000..b1b1c45a --- /dev/null +++ b/src/views/component-viewer/model/scvd-gui-interface.ts @@ -0,0 +1,25 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ScvdGuiInterface { + getGuiEntry(): { name: string | undefined, value: string | undefined }; + getGuiChildren(): ScvdGuiInterface[]; + getGuiName(): string | undefined ; + getGuiValue(): string | undefined; + getGuiConditionResult(): boolean; + getGuiLineInfo(): string | undefined; + hasGuiChildren(): boolean; +} diff --git a/src/views/component-viewer/model/scvd-item.ts b/src/views/component-viewer/model/scvd-item.ts new file mode 100644 index 00000000..745a3c4d --- /dev/null +++ b/src/views/component-viewer/model/scvd-item.ts @@ -0,0 +1,198 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdPrint } from './scvd-print'; +import { ScvdValueOutput } from './scvd-value-output'; +import { ScvdCondition } from './scvd-condition'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; +import { ScvdListOut } from './scvd-list-out'; + +export class ScvdItem extends ScvdNode { + private _property: ScvdValueOutput | undefined; + private _value: ScvdValueOutput | undefined; + private _cond: ScvdCondition | undefined; + private _bold: ScvdCondition | undefined; + private _alert: ScvdCondition | undefined; + private _item: ScvdItem[] = []; // Array of child items + private _listOut: ScvdListOut[] = []; // Array of child lists + private _print: ScvdPrint[] = []; // Array of child prints + + constructor( + parent: ScvdNode | undefined, + cond?: string, + bold?: string, + alert?: string, + ) { + super(parent); + if (cond !== undefined) { + this._cond = new ScvdCondition(this, cond); + } + if (bold !== undefined) { + this._bold = new ScvdCondition(this, bold); + } + if (alert !== undefined) { + this._alert = new ScvdCondition(this, alert); + } + } + + public override get classname(): string { + return 'ScvdItem'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.property = getStringFromJson(xml.property); + this.value = getStringFromJson(xml.value); + this.cond = getStringFromJson(xml.cond); + this.bold = getStringFromJson(xml.bold); + this.alert = getStringFromJson(xml.alert); + + const item = getArrayFromJson(xml.item); + item?.forEach( (v: Json) => { + const newItem = this.addItem(); + newItem.readXml(v); + }); + + const listOut = getArrayFromJson(xml.list); + listOut?.forEach( (v: Json) => { + const newListOut = this.addListOut(); + newListOut.readXml(v); + }); + + const print = getArrayFromJson(xml.print); + print?.forEach( (v: Json) => { + const printItem = this.addPrint(); + printItem.readXml(v); + }); + + return super.readXml(xml); + } + + public set property(value: string | undefined) { + if (value !== undefined) { + this._property = new ScvdValueOutput(this, value, 'property'); + return; + } + } + public get property(): ScvdValueOutput | undefined { + return this._property; + } + + public get value(): ScvdValueOutput | undefined { + return this._value; + } + public set value(value: string | undefined) { + if (value !== undefined) { + this._value = new ScvdValueOutput(this, value, 'value'); + return; + } + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + + public set cond(value: string | undefined) { + if (value !== undefined) { + this._cond = new ScvdCondition(this, value); + return; + } + } + + public override async getConditionResult(): Promise { + if (this._cond) { + return await this._cond.getResult(); + } + return super.getConditionResult(); + } + + public get bold(): ScvdCondition | undefined { + return this._bold; + } + + public set bold(value: string | undefined) { + if (value !== undefined) { + this._bold = new ScvdCondition(this, value); + return; + } + } + + public get alert(): ScvdCondition | undefined { + return this._alert; + } + + public set alert(value: string | undefined) { + if (value !== undefined) { + this._alert = new ScvdCondition(this, value); + return; + } + } + + public get listOut(): ScvdListOut[] { + return this._listOut; + } + public addListOut(): ScvdListOut { + const newItem = new ScvdListOut(this); + this._listOut.push(newItem); + return newItem; + } + + public get item(): ScvdItem[] { + return this._item; + } + + public addItem(): ScvdItem { + const newItem = new ScvdItem(this); + this._item.push(newItem); + return newItem; + } + + public get print(): ScvdPrint[] { + return this._print; + } + public addPrint(): ScvdPrint { + const item = new ScvdPrint(this); + this._print.push(item); + return item; + } + + public override async getGuiName(): Promise { + if (this.property === undefined) { + return undefined; + } + return await this.property.getGuiValue(); + } + + public hasGuiChildren(): boolean { + return this.item.length > 0 || this.listOut.length > 0 || this.print.length > 0; + } + + public override async getGuiValue(): Promise { + if (this.value === undefined) { + return undefined; + } + return await this.value.getGuiValue(); + } + + +} diff --git a/src/views/component-viewer/model/scvd-list-out.ts b/src/views/component-viewer/model/scvd-list-out.ts new file mode 100644 index 00000000..52c20d38 --- /dev/null +++ b/src/views/component-viewer/model/scvd-list-out.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdItem } from './scvd-item'; +import { ScvdList } from './scvd-list'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; + +export class ScvdListOut extends ScvdList { + private _item: ScvdItem[] = []; + private _listOut: ScvdListOut[] = []; // Array of child lists + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdListOut'; + } + + // class is derived from ScvdList, but we do not want all properties of ScvdList to be settable from XML + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.start = getStringFromJson(xml.start); + this.limit = getStringFromJson(xml.limit); + this.while = getStringFromJson(xml.while); + this.cond = getStringFromJson(xml.cond); + + + const items = getArrayFromJson(xml.item); + items?.forEach(item => { + const itemObj = this.addItem(); + itemObj.readXml(item); + }); + + const lists = getArrayFromJson(xml.list); + lists?.forEach(list => { + const listItem = this.addList(); + listItem.readXml(list); + }); + + return super.readXml(xml); + } + + public verify(): boolean { + return super.verify(); + } + + public get item(): ScvdItem[] { + return this._item; + } + + public addItem(): ScvdItem { + const item = new ScvdItem(this); + this._item.push(item); + return item; + } + + public get listOut(): ScvdListOut[] { + return this._listOut; + } + public addListOut(): ScvdListOut { + const newItem = new ScvdListOut(this); + this._listOut.push(newItem); + return newItem; + } + + public override async getGuiName(): Promise { + return undefined; + } +} diff --git a/src/views/component-viewer/model/scvd-list.ts b/src/views/component-viewer/model/scvd-list.ts new file mode 100644 index 00000000..fd3b5b0f --- /dev/null +++ b/src/views/component-viewer/model/scvd-list.ts @@ -0,0 +1,213 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdCalc } from './scvd-calc'; +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdRead } from './scvd-read'; +import { ScvdReadList } from './scvd-readlist'; +import { ScvdVar } from './scvd-var'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; +import { ScvdCondition } from './scvd-condition'; + +export class ScvdList extends ScvdNode { + private _start: ScvdExpression | undefined = undefined; + private _limit: ScvdExpression | undefined = undefined; + private _while: ScvdExpression | undefined = undefined; + private _cond: ScvdCondition | undefined = undefined; + + private _list: ScvdList[] = []; + private _readlist: ScvdReadList[] = []; + private _read: ScvdRead[] = []; + private _var: ScvdVar[] = []; + private _calc: ScvdCalc[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdList'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.start = getStringFromJson(xml.start); + this.limit = getStringFromJson(xml.limit); + this.while = getStringFromJson(xml.while); + this.cond = getStringFromJson(xml.cond); + + const lists = getArrayFromJson(xml.list); + lists?.forEach(list => { + const listItem = this.addList(); + listItem.readXml(list); + }); + + const readLists = getArrayFromJson(xml.readlist); + readLists?.forEach(readList => { + const readListItem = this.addReadList(); + readListItem.readXml(readList); + this.addToSymbolContext(readListItem.name, readListItem); + }); + + const reads = getArrayFromJson(xml.read); + reads?.forEach(read => { + const readItem = this.addRead(); + readItem.readXml(read); + this.addToSymbolContext(readItem.name, readItem); + }); + + const vars = getArrayFromJson(xml.var); + vars?.forEach(v => { + const varItem = this.addVar(); + varItem.readXml(v); + this.addToSymbolContext(varItem.name, varItem); + }); + + const calcs = getArrayFromJson(xml.calc); + calcs?.forEach(c => { + const calcItem = this.addCalc(); + calcItem.readXml(c); + }); + + return super.readXml(xml); + } + + public verify(): boolean { + if (this._limit && this._while) { + console.error('List cannot have both limit and while attributes'); + return false; + } + return true; + } + + public get start(): ScvdExpression | undefined { + return this._start; + } + + public set start(value: string | undefined) { + if (value !== undefined) { + this._start = new ScvdExpression(this, value, 'start'); + } + } + + public get limit(): ScvdExpression | undefined { + return this._limit; + } + + public set limit(value: string | undefined) { + if (value !== undefined) { + this._limit = new ScvdExpression(this, value, 'limit'); + } + } + + public get while(): ScvdExpression | undefined { + return this._while; + } + + public set while(value: string | undefined) { + if (value !== undefined) { + this._while = new ScvdExpression(this, value, 'while'); + } + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + + public set cond(value: string | undefined) { + if (value !== undefined) { + this._cond = new ScvdCondition(this, value); + } + } + + public override async getConditionResult(): Promise { + if (this._cond) { + const cond = await this._cond.getResult(); + return cond; + } + return super.getConditionResult(); + } + + public override applyInit(): boolean { + return true; + } + + public addList(): ScvdList { + const listItem = new ScvdList(this); + this._list.push(listItem); + return listItem; + } + + public get list(): ScvdList[] { + return this._list; + } + + public addReadList(): ScvdReadList { + const readListItem = new ScvdReadList(this); + this._readlist.push(readListItem); + return readListItem; + } + + public get readList(): ScvdReadList[] { + return this._readlist; + } + + public addRead(): ScvdRead { + const readItem = new ScvdRead(this); + this._read.push(readItem); + this.addToSymbolContext(readItem.name, readItem); + return readItem; + } + public get read(): ScvdRead[] { + return this._read; + } + public addVar(): ScvdVar { + const varItem = new ScvdVar(this); + this._var.push(varItem); + return varItem; + } + public get var(): ScvdVar[] { + return this._var; + } + public addCalc(): ScvdCalc { + const calcItem = new ScvdCalc(this); + this._calc.push(calcItem); + return calcItem; + } + public get calc(): ScvdCalc[] { + return this._calc; + } + + public override getSymbol(name: string): ScvdNode | undefined { + return this.symbolsCache( + name, + this.var.find(s => s.name === name) ?? + this.read.find(s => s.name === name) ?? + this.readList.find(s => s.name === name) + ) ?? this.parent?.getSymbol(name); + } + + +} diff --git a/src/views/component-viewer/model/scvd-member.ts b/src/views/component-viewer/model/scvd-member.ts new file mode 100644 index 00000000..482a23e0 --- /dev/null +++ b/src/views/component-viewer/model/scvd-member.ts @@ -0,0 +1,167 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_member.html + +import { NumberType, NumberTypeInput } from './number-type'; +import { ScvdDataType } from './scvd-data-type'; +import { ScvdEnum } from './scvd-enum'; +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; + +// Offset to base address in [Bytes]. Use the uVision debug dialog Symbols to find the offset. You can use Expressions. +// For imported members, the offset is recalculated. Refer to the description of attribute import in typedef. +export class ScvdMember extends ScvdNode { + private _type: ScvdDataType | undefined; + private _offset: ScvdExpression | undefined; + private _size: number | undefined; + private _enum: ScvdEnum[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdMember'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.type = getStringFromJson(xml.type); + this.offset = getStringFromJson(xml.offset); + this.size = getStringFromJson(xml.size); + + const enums = getArrayFromJson(xml.enum); + enums?.forEach(enumItem => { + const newEnum = this.addEnum(); + newEnum.readXml(enumItem); + }); + + return super.readXml(xml); + } + + public get type(): ScvdDataType | undefined { + return this._type; + } + + public set type(value: string | undefined) { + if (value !== undefined) { + this._type = new ScvdDataType(this, value); + } + } + + public get offset(): ScvdExpression | undefined { + return this._offset; + } + + public set offset(value: string | undefined) { + if (value !== undefined) { + this._offset = new ScvdExpression(this, value, 'offset'); + } + } + + public get size(): number | undefined { + return this._size; + } + + public set size(value: NumberTypeInput | undefined) { + if (value !== undefined) { + this._size = new NumberType(value).value; + } + } + + public override getTypeSize(): number | undefined { + return this._type?.getTypeSize(); + } + + public override getVirtualSize(): number | undefined { + return this.getTargetSize(); + } + + public override getIsPointer(): boolean { + return this.type?.getIsPointer() ?? false; + } + + // if size is set, this is the size in byte to be read from target + public override getTargetSize(): number | undefined { + const isPointer = this.getIsPointer(); + if (isPointer) { + return 4; // pointer size + } + return this.size ?? this.getTypeSize(); + } + + public addEnum(): ScvdEnum { + const lastEnum = this._enum[this._enum.length - 1]; + const enumItem = new ScvdEnum(this, lastEnum); + this._enum.push(enumItem); + return enumItem; + } + public get enum(): ScvdEnum[] { + return this._enum; + } + + public async getEnum(value: number): Promise { + for (const item of this._enum) { + const enumVal = await item.value?.getValue(); + if (typeof enumVal === 'number' && enumVal === value) { + return item; + } + } + return undefined; + } + + // search a member (member, var) in typedef + public override getMember(_property: string): ScvdNode | undefined { + const type = this._type; + if (type !== undefined) { + const typeObj = type.getMember(_property); + return typeObj; + } + return undefined; + } + + // member’s byte offset + public override async getMemberOffset(): Promise { + const offsetExpr = this._offset; + if (offsetExpr !== undefined) { + const offsetValue = await offsetExpr.getValue(); + if (typeof offsetValue === 'number') { + return offsetValue; + } + } + return 0; // TOIMPL: default? + } + + public override isPointerRef(): boolean { + const type = this._type?.type; + if (type !== undefined) { + return type.isPointer; + } + return false; + } + + public override getValueType(): string | undefined { + return this.type?.getValueType(); + } +} diff --git a/src/views/component-viewer/model/scvd-node.ts b/src/views/component-viewer/model/scvd-node.ts new file mode 100644 index 00000000..299b025d --- /dev/null +++ b/src/views/component-viewer/model/scvd-node.ts @@ -0,0 +1,208 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ResolveSymbolCb } from '../resolver'; +import { ExecutionContext } from '../scvd-eval-context'; +import { getLineNumberFromJson, getStringField } from './scvd-utils'; +import { Json, ScvdBase } from './scvd-base'; + +/** + * Model-aware node that carries symbol resolution, execution context hooks and + * size/value helpers. Keeps ScvdBase focused on tree wiring. + */ +export abstract class ScvdNode extends ScvdBase { + private _symbolsCache: Map | undefined; + + public override get parent(): ScvdNode | undefined { + return super.parent as ScvdNode | undefined; + } + + public override get children(): ScvdNode[] { + return super.children as ScvdNode[]; + } + + constructor(parent: ScvdBase | undefined) { + super(parent); + } + + public override get classname(): string { + return 'ScvdNode'; + } + + public readXml(xml: Json): boolean { + if (xml === undefined ) { + this.tag = 'XML undefined'; + return false; + } + this.lineNo = getLineNumberFromJson(xml); + const tag = getStringField(xml, '#Name') ?? getStringField(xml, '#name'); + if (tag === undefined) { + if (Array.isArray(xml)) { + const first = xml[0] as Json | undefined; + const subTag = getStringField(first, '#Name') ?? getStringField(first, '#name') ?? getStringField(first, 'tag'); + if (subTag !== undefined) { + this.tag = subTag + '[]'; + } else { + this.tag = 'Array[]'; + } + } else if (this.tag === undefined) { + this.tag = 'unknown-tag'; + } + } else { + this.tag = tag; + } + this.name = getStringField(xml, 'name'); + this.info = getStringField(xml, 'info'); + + return true; + } + + public override addToSymbolContext(name: string | undefined, symbol: ScvdNode): void { + this.parent?.addToSymbolContext(name, symbol); + } + + // search for symbol in parent chain + public override getSymbol(name: string): ScvdNode | undefined { + return this.parent?.getSymbol(name); + } + + // search a member (member, var) in typedef + public getMember(_property: string): ScvdNode | undefined { + return undefined; + } + + public getElementRef(): ScvdNode | undefined { + return undefined; + } + + public setExecutionContext(_executionContext: ExecutionContext) { + } + + // default condition always true + public async getConditionResult(): Promise { + return true; + } + + // Member function available to all ScvdItems and derived classes + public resolveAndLink(_resolveFunc: ResolveSymbolCb): boolean { + // Default implementation does nothing, can be overridden by subclasses + return false; + } + + public applyInit(): boolean { + // Default implementation does nothing, can be overridden by subclasses + return true; + } + + // expanded values + public async getValue(): Promise { + return undefined; // TOIMPL: change to undefined to indicate no value + } + + public async setValue(val: number | string): Promise { + return val; + } + + public writeAt(byteOffset: number, widthBits: number, value: number | string): number | string | undefined { + console.error(`WriteAt not implemented: item=${this.classname}: ${this.getDisplayLabel()}, offset=${byteOffset}, width=${widthBits}, value=${value}`); + return undefined; + } + + public readAt(byteOffset: number, widthBits: number): number | string | undefined { + console.error(`ReadAt not implemented: item=${this.classname}: ${this.getDisplayLabel()}, offset=${byteOffset}, width=${widthBits}`); + return undefined; + } + + public getTargetSize(): number | undefined { + console.error(`GetTargetSize not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return undefined; + } + public getTypeSize(): number | undefined { + console.error(`GetTypeSize not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return undefined; + } + public getVirtualSize(): number | undefined { + console.error(`GetVirtualSize not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return undefined; + } + public getIsPointer(): boolean { + console.error(`GetIsPointer not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return false; + } + public async getArraySize(): Promise { + return Promise.resolve(undefined); // default + } + + // member’s byte offset + public async getMemberOffset(): Promise { + console.error(`GetMemberOffset not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return undefined; + } + + public getElementBitWidth(): number | undefined { + console.error(`GetElementBitWidth not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return undefined; + } + + public getValueType(): string | undefined { + console.error(`GetValueType not implemented: item=${this.classname}: ${this.getDisplayLabel()}`); + return undefined; + } + + // base implementation assumes not a pointer ref + public isPointerRef(): boolean { + return false; + } + + // ------------ GUI Interface Begin ------------ + public async getGuiName(): Promise { + return this.name; + } + + public async getGuiValue(): Promise { + const val = await this.getValue(); + if (val !== undefined) { + if (typeof val === 'number' || typeof val === 'bigint') { + return val.toString(); + } else if (typeof val === 'string') { + return val; + } + } + return undefined; + } + + public getGuiConditionResult(): boolean { + return true; // use getConditionResult() later + } + + public getGuiLineInfo(): string { + return this.getLineInfoStr(); + } + // ------------ GUI Interface End ------------ + + protected symbolsCache(key: string, value: ScvdNode | undefined): ScvdNode | undefined { + return this._symbolsCache?.get(key) ?? (value !== undefined ? ((this._symbolsCache ??= new Map()).set(key, value), value) : undefined); + } + + protected clearSymbolsCache(): void { + this._symbolsCache = undefined; // let GC reclaim the Map + } + + protected clearSymbolCachesRecursive(): void { + this.clearSymbolsCache(); + this.children.forEach(child => child.clearSymbolCachesRecursive()); + } +} diff --git a/src/views/component-viewer/model/scvd-object.ts b/src/views/component-viewer/model/scvd-object.ts new file mode 100644 index 00000000..f3a0752f --- /dev/null +++ b/src/views/component-viewer/model/scvd-object.ts @@ -0,0 +1,237 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_objects.html + +import { ScvdCalc } from './scvd-calc'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdList } from './scvd-list'; +import { ScvdOut } from './scvd-out'; +import { ScvdRead } from './scvd-read'; +import { ScvdReadList } from './scvd-readlist'; +import { ScvdVar } from './scvd-var'; +import { getArrayFromJson } from './scvd-utils'; + +export class ScvdObjects extends ScvdNode { + private _objects: ScvdObject[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdObjects'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + const objects = getArrayFromJson(xml.object); + objects?.forEach( (v: Json) => { + const object = this.addObject(); + object.readXml(v); + }); + + return super.readXml(xml); + } + + public addObject(): ScvdObject { + const object = new ScvdObject(this); + this._objects.push(object); + return object; + } + + public get objects(): ScvdObject[] { + return this._objects; + } + + // currently no global context above object level + public override getSymbol(_name: string): ScvdNode | undefined { + return undefined; + } + +} + +export class ScvdObject extends ScvdNode { + private _var: ScvdVar[] = []; + private _calc: ScvdCalc[] = []; + private _list: ScvdList[] = []; + private _read: ScvdRead[] = []; + private _readList: ScvdReadList[] = []; + private _out: ScvdOut[] = []; + private _symbolContext: Map = new Map(); + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdObject'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + const vars = getArrayFromJson(xml?.var); + vars?.forEach( (v: Json) => { + const varItem = this.addVar(); + varItem.readXml(v); + this.addToSymbolContext(varItem.name, varItem); + }); + + const calcs = getArrayFromJson(xml?.calc); + calcs?.forEach( (c: Json) => { + const calcItem = this.addCalc(); + calcItem.readXml(c); + }); + + const lists = getArrayFromJson(xml?.list); + lists?.forEach( (l: Json) => { + const listItem = this.addList(); + listItem.readXml(l); + }); + + const reads = getArrayFromJson(xml?.read); + reads?.forEach( (r: Json) => { + const readItem = this.addRead(); + readItem.readXml(r); + this.addToSymbolContext(readItem.name, readItem); + }); + + const readLists = getArrayFromJson(xml?.readlist); + readLists?.forEach( (rl: Json) => { + const readListItem = this.addReadList(); + readListItem.readXml(rl); + this.addToSymbolContext(readListItem.name, readListItem); + }); + + const outs = getArrayFromJson(xml?.out); + outs?.forEach( (o: Json) => { + const outItem = this.addOut(); + outItem.readXml(o); + }); + + return super.readXml(xml); + } + + public get list(): ScvdList[] { + return this._list; + } + + public get read(): ScvdRead[] { + return this._read; + } + + public get readList(): ScvdReadList[] { + return this._readList; + } + + public get var(): ScvdVar[] { + return this._var; + } + + public get out(): ScvdOut[] { + return this._out; + } + + public get symbolContext(): Map { + return this._symbolContext; + } + + public override addToSymbolContext(name: string | undefined, symbol: ScvdNode): void { + if (name !== undefined && this.symbolContext.has(name) === false) { + this.symbolContext.set(name, symbol); + } + } + + // all symbols are stored in object context, except vars in typedefs + public override getSymbol(name: string): ScvdNode | undefined { + const symbol = this.symbolContext.get(name); + return symbol; + } + + public getVar(name: string): ScvdVar | undefined { + for (const v of this._var) { + if (v.name === name) { + return v; + } + } + + return undefined; + } + + public getRead(name: string): ScvdRead | undefined { + for (const r of this._read) { + if (r.name === name) { + return r; + } + } + + return undefined; + } + + public addVar(): ScvdVar { + const varItem = new ScvdVar(this); + this._var.push(varItem); + return varItem; + } + public get vars(): ScvdVar[] { + return this._var; + } + + public addCalc(): ScvdCalc { + const calcItem = new ScvdCalc(this); + this._calc.push(calcItem); + return calcItem; + } + public get calcs(): ScvdCalc[] { + return this._calc; + } + + public addList(): ScvdList { + const listItem = new ScvdList(this); + this._list.push(listItem); + return listItem; + } + + + public addRead(): ScvdRead { + const readItem = new ScvdRead(this); + this._read.push(readItem); + return readItem; + } + + public addReadList(): ScvdReadList { + const readListItem = new ScvdReadList(this); + this._readList.push(readListItem); + return readListItem; + } + + public addOut(): ScvdOut { + const outItem = new ScvdOut(this); + this._out.push(outItem); + return outItem; + } + +} diff --git a/src/views/component-viewer/model/scvd-out.ts b/src/views/component-viewer/model/scvd-out.ts new file mode 100644 index 00000000..1545a413 --- /dev/null +++ b/src/views/component-viewer/model/scvd-out.ts @@ -0,0 +1,128 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdDataType } from './scvd-data-type'; +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdCondition } from './scvd-condition'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; +import { ScvdItem } from './scvd-item'; +import { ScvdListOut } from './scvd-list-out'; + +export class ScvdOut extends ScvdNode { + private _value: ScvdExpression | undefined; // name._value — expression that evaluates to the value of the output. + private _type: ScvdDataType | undefined; + private _cond: ScvdCondition | undefined; + private _item: ScvdItem[] = []; + private _listOut: ScvdListOut[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdOut'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.value = getStringFromJson(xml.value); + this.type = getStringFromJson(xml.type); + this.cond = getStringFromJson(xml.cond); + + const item = getArrayFromJson(xml.item); + item?.forEach((it: Json) => { + const newItem = this.addItem(); + newItem.readXml(it); + }); + + const list = getArrayFromJson(xml.list); + list?.forEach((it: Json) => { + const newList = this.addList(); + newList.readXml(it); + }); + + return super.readXml(xml); + } + + public set value(value: string | undefined) { + if (value !== undefined) { + this._value = new ScvdExpression(this, value, 'value'); + } + } + public get value(): ScvdExpression | undefined { + return this._value; + } + public set type(value: string | undefined) { + if (value !== undefined) { + if ( this._type === undefined) { + this._type = new ScvdDataType(this, value); + return; + } + this._type.type = value; + } + } + public get type(): ScvdDataType | undefined { + return this._type; + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + public set cond(value: string | undefined) { + if (value !== undefined) { + this._cond = new ScvdCondition(this, value); + return; + } + } + + public override async getConditionResult(): Promise { + if (this._cond) { + return await this._cond.getResult(); + } + return super.getConditionResult(); + } + + public get item(): ScvdItem[] { + return this._item; + } + public addItem(): ScvdItem { + const newItem = new ScvdItem(this); + this._item.push(newItem); + return newItem; + } + + public get list(): ScvdListOut[] { + return this._listOut; + } + public addList(): ScvdListOut { + const list = new ScvdListOut(this); + this._listOut.push(list); + return list; + } + + public override getValueType(): string | undefined { + return this.type?.getValueType(); + } +} diff --git a/src/views/component-viewer/model/scvd-print-expression.ts b/src/views/component-viewer/model/scvd-print-expression.ts new file mode 100644 index 00000000..405377fb --- /dev/null +++ b/src/views/component-viewer/model/scvd-print-expression.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/scvd_expression.html + + +import { ScvdNode } from './scvd-node'; +import { ScvdExpression } from './scvd-expression'; + +export class ScvdPrintExpression extends ScvdExpression { + + constructor( + parent: ScvdNode | undefined, + expression: string | undefined, + scvdVarName: string, + ) { + super(parent, expression, scvdVarName, true); + this.expression = expression; + } + + public override get classname(): string { + return 'ScvdPrintExpression'; + } + + public override configure(): boolean { + return super.configure(); + } + + public override validate(prevResult: boolean): boolean { + return super.validate(prevResult && true); + } + + public override async getGuiName(): Promise { + return super.getGuiName(); + } + + public override async getGuiValue(): Promise { + return super.getGuiValue(); + } +} diff --git a/src/views/component-viewer/model/scvd-print.ts b/src/views/component-viewer/model/scvd-print.ts new file mode 100644 index 00000000..4440bd21 --- /dev/null +++ b/src/views/component-viewer/model/scvd-print.ts @@ -0,0 +1,129 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdValueOutput } from './scvd-value-output'; +import { ScvdCondition } from './scvd-condition'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { getStringFromJson } from './scvd-utils'; + +export class ScvdPrint extends ScvdNode { + private _cond: ScvdCondition | undefined; + private _property: ScvdValueOutput | undefined; + private _value: ScvdValueOutput | undefined; + private _bold: ScvdCondition | undefined; // default 1 + private _alert: ScvdCondition | undefined; // default 0 + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdPrint'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.cond = getStringFromJson(xml.cond); + this.property = getStringFromJson(xml.property); + this.value = getStringFromJson(xml.value); + this.bold = getStringFromJson(xml.bold); + this.alert = getStringFromJson(xml.alert); + + return super.readXml(xml); + } + public get property(): ScvdValueOutput | undefined { + return this._property; + } + public set property(value: string | undefined) { + if (value !== undefined) { + this._property = new ScvdValueOutput(this, value, 'property'); + return; + } + } + + public get value(): ScvdValueOutput | undefined { + return this._value; + } + public set value(value: string | undefined) { + if (value !== undefined) { + this._value = new ScvdValueOutput(this, value, 'value'); + return; + } + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + public set cond(value: string | undefined) { + if (value !== undefined) { + this._cond = new ScvdCondition(this, value); + return; + } + } + + public override async getConditionResult(): Promise { + if (this._cond) { + return await this._cond.getResult(); + } + return super.getConditionResult(); + } + + public get bold(): ScvdCondition | undefined { + return this._bold; + } + public set bold(value: string | undefined) { + if (value !== undefined) { + this._bold = new ScvdCondition(this, value); + return; + } + } + + public get alert(): ScvdCondition | undefined { + return this._alert; + } + public set alert(value: string | undefined) { + if (value !== undefined) { + this._alert = new ScvdCondition(this, value); + return; + } + } + + // Main Display functions + public override async getGuiName(): Promise { + if (this.property === undefined) { + return undefined; + } + return await this.property.getGuiName(); + } + + public override async getGuiValue(): Promise { + if (this.value === undefined) { + return undefined; + } + return await this.value.getGuiValue(); + } + + + +} diff --git a/src/views/component-viewer/model/scvd-read.ts b/src/views/component-viewer/model/scvd-read.ts new file mode 100644 index 00000000..41575b3e --- /dev/null +++ b/src/views/component-viewer/model/scvd-read.ts @@ -0,0 +1,180 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { NumberType, NumberTypeInput } from './number-type'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdEndian } from './scvd-endian'; +import { ScvdExpression } from './scvd-expression'; +import { ScvdCondition } from './scvd-condition'; +import { ScvdSymbol } from './scvd-symbol'; +import { ScvdDataType } from './scvd-data-type'; +import { getStringFromJson } from './scvd-utils'; + +export class ScvdRead extends ScvdNode { + private _type: ScvdDataType | undefined; + private _size: ScvdExpression | undefined; + private _symbol: ScvdSymbol | undefined; + private _offset: ScvdExpression | undefined; + private _const: boolean = false; // Variables with attribute const set to "1" are constants that are read only once after debugger start. Default value is 0. + private _cond: ScvdCondition | undefined; + private _endian: ScvdEndian | undefined; + static readonly ARRAY_SIZE_MIN = 1; + static readonly ARRAY_SIZE_MAX = 512; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdRead'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.type = getStringFromJson(xml.type); + this.symbol = getStringFromJson(xml.symbol); + this.offset = getStringFromJson(xml.offset); + this.const = getStringFromJson(xml.const); + this.cond = getStringFromJson(xml.cond); + this.size = getStringFromJson(xml.size); + this.endian = getStringFromJson(xml.endian); + + return super.readXml(xml); + } + + public get type(): ScvdDataType | undefined { + return this._type; + } + public set type(value: string | undefined) { + if (value !== undefined) { + this._type = new ScvdDataType(this, value); + } + } + + public set symbol(name: string | undefined) { + if (this._symbol === undefined && name !== undefined) { + this._symbol = new ScvdSymbol(this, name); + return; + } + } + + public get symbol(): ScvdSymbol | undefined { + return this._symbol; + } + + public set offset(value: string | undefined) { + if (value !== undefined) { + this._offset = new ScvdExpression(this, value, 'offset'); + return; + } + } + + public get offset(): ScvdExpression | undefined { + return this._offset; + } + + public set const(value: NumberTypeInput | undefined) { + if (value !== undefined) { + this._const = new NumberType(value).value ? true : false; + } + } + + public get const(): boolean { + return this._const; + } + + public set cond(value: string | undefined) { + if (value !== undefined) { + this._cond = new ScvdCondition(this, value); + return; + } + } + + public get cond(): ScvdCondition | undefined { + return this._cond; + } + + public override async getConditionResult(): Promise { + if (this._cond) { + return await this._cond.getResult(); + } + return super.getConditionResult(); + } + + public get size(): ScvdExpression | undefined { + return this._size; + } + + public override getTargetSize(): number | undefined { + return this.type?.getTypeSize(); + } + + public override getVirtualSize(): number | undefined { + return this.type?.getVirtualSize(); + } + public override async getArraySize(): Promise { + const v = await this.size?.getValue(); + if (typeof v === 'bigint') { + return Number(v); + } + if (typeof v === 'number') { + return v; + } + return undefined; + } + + public override getIsPointer(): boolean { + return this.type?.getIsPointer() ?? false; + } + + + public set size(value: string | undefined) { + if (value !== undefined) { + this._size = new ScvdExpression(this, value, 'size'); + return; + } + } + + public get endian(): ScvdEndian | undefined { + return this._endian; + } + public set endian(value: string | undefined) { + if (value !== undefined) { + if ( this._endian === undefined) { + this._endian = new ScvdEndian(this, value); + return; + } + this._endian.endian = value; + } + } + + public override getMember(property: string): ScvdNode | undefined { + return this.type?.getMember(property); + } + + public override getValueType(): string | undefined { + return this.type?.getValueType(); + } + +} diff --git a/src/views/component-viewer/model/scvd-readlist.ts b/src/views/component-viewer/model/scvd-readlist.ts new file mode 100644 index 00000000..ac3ec896 --- /dev/null +++ b/src/views/component-viewer/model/scvd-readlist.ts @@ -0,0 +1,160 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_readlist.html + +import { NumberType, NumberTypeInput } from './number-type'; +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdRead } from './scvd-read'; +import { getStringFromJson } from './scvd-utils'; +import { ResolveSymbolCb, ResolveType } from '../resolver'; + + +// readlist defines a list of variables or arrays. The first instance of will define 'var', +// the following use of will use the definition. + +export class ScvdReadList extends ScvdRead { + private _count: ScvdExpression | undefined; // default is 1 + private _next: string | undefined; // member name for the .next pointer + private _init: number = 0; // When init="1" previous read items in the list are discarded. Default value is 0. + private _based: number = 0; // When based="1" the attribute symbol and attribute offset specifies a pointer (or pointer array). Default value is 0. + + static readonly READ_SIZE_MIN = 1; + static readonly READ_SIZE_MAX = 1024; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdReadList'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.count = getStringFromJson(xml.count); + this.next = getStringFromJson(xml.next); + this.init = getStringFromJson(xml.init); + this.based = getStringFromJson(xml.based); + + return super.readXml(xml); + } + + + public set count(value: string | undefined) { + if (value !== undefined) { + this._count = new ScvdExpression(this, value, 'count'); + } + } + public get count(): ScvdExpression | undefined { + return this._count; + } + + public set next(name: string | undefined) { + this._next = name; + } + public get next(): string | undefined { + return this._next; + } + + public set init(value: NumberTypeInput | undefined) { + if (value !== undefined) { + this._init = new NumberType(value).value; + } + } + public get init(): number { + return this._init; + } + + public set based(value: NumberTypeInput | undefined) { + if (value !== undefined) { + this._based = new NumberType(value).value; + } + } + public get based(): number { + return this._based; + } + + public override getTargetSize(): number | undefined { + return this.type?.getTypeSize(); + } + + public override getVirtualSize(): number | undefined { + return this.type?.getVirtualSize(); + } + + // A readlist is considered a pointer if based="1" + public override getIsPointer(): boolean { + const based = this.based ? true : false; + const typeIsPointer = this.type?.getIsPointer(); + return based || (typeIsPointer ?? false); + } + + public override resolveAndLink(resolveFunc: ResolveSymbolCb): boolean { + if (this._next !== undefined) { + // Ensure the referenced member exists; log if missing + const typedef = resolveFunc(this._next, ResolveType.localType); + const member = typedef ? resolveFunc(this._next, ResolveType.localMember, typedef) : undefined; + if (member === undefined && typedef) { + console.error(`${this.getLineNoStr()}: Resolving readlist .next: could not find member '${this._next}' in typedef '${typedef.name}'`); + } + } + return super.resolveAndLink(resolveFunc); + } + + public override applyInit(): boolean { + if (this.init === 1) { + // Discard previous read objects + return true; + } + + return true; + } + + public async getCount(): Promise { + if (this._count === undefined) { + return undefined; + } + const v = await this._count.getValue(); + const num = v !== undefined ? Number(v) : undefined; + if (num === undefined || Number.isNaN(num)) { + return undefined; + } + if (num < ScvdReadList.READ_SIZE_MIN) { + return ScvdReadList.READ_SIZE_MIN; + } + if (num > ScvdReadList.READ_SIZE_MAX) { + return ScvdReadList.READ_SIZE_MAX; + } + return num; + } + + public getNext(): string | undefined { + return this._next; + } + + public getInit(): number { + return this._init; + } + +} diff --git a/src/views/component-viewer/model/scvd-symbol.ts b/src/views/component-viewer/model/scvd-symbol.ts new file mode 100644 index 00000000..584b0256 --- /dev/null +++ b/src/views/component-viewer/model/scvd-symbol.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdNode } from './scvd-node'; +import { MemberInfo } from '../scvd-debug-target'; + +export class ScvdSymbol extends ScvdNode { + private _symbol: string | undefined; + private _executionContext: ExecutionContext | undefined; + private _address: number | undefined; + private _memberInfo: MemberInfo[] = []; + + constructor( + parent: ScvdNode | undefined, + value: string, + ) { + super(parent); + this.symbol = value; + } + + public override get classname(): string { + return 'ScvdSymbol'; + } + + public get symbol(): string | undefined { + return this._symbol; + } + public set symbol(value: string | undefined) { + this._symbol = value; + } + + public get address(): number | undefined { + return this._address; + } + public set address(value: number | undefined) { + this._address = value; + } + + public get memberInfo(): MemberInfo[] { + return this._memberInfo; + } + private addMemberInfo(name: string, size: number, offset: number) { + this._memberInfo.push({ name: name, size: size, offset: offset }); + } + + public async fetchSymbolInformation(): Promise { + if (this.symbol === undefined || this._executionContext === undefined) { + return false; + } + + const symbolInfo = await this._executionContext.debugTarget.getSymbolInfo(this.symbol); + if (symbolInfo !== undefined) { + this.address = symbolInfo.address; + symbolInfo.member?.forEach(member => { + this.addMemberInfo(member.name, member.size, member.offset); + }); + } + + return true; + } + + public getOffset(name: string | undefined): number | undefined { + if (name === undefined || this.memberInfo === undefined) { + return undefined; + } + + const memberInfo = this.memberInfo.find(member => member.name === name); + return memberInfo?.offset; + } + + public override setExecutionContext(executionContext: ExecutionContext) { + this._executionContext = executionContext; + } + + +} diff --git a/src/views/component-viewer/model/scvd-template.ts b/src/views/component-viewer/model/scvd-template.ts new file mode 100644 index 00000000..b6451fa8 --- /dev/null +++ b/src/views/component-viewer/model/scvd-template.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; + +export class ScvdTemplate extends ScvdNode { + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + //this.tag = xml.tag; + + return super.readXml(xml); + } + + public override get classname(): string { + return 'ScvdTemplate'; + } + +} diff --git a/src/views/component-viewer/model/scvd-typedef.ts b/src/views/component-viewer/model/scvd-typedef.ts new file mode 100644 index 00000000..7526c42e --- /dev/null +++ b/src/views/component-viewer/model/scvd-typedef.ts @@ -0,0 +1,288 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_typedefs.html + +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { ScvdMember } from './scvd-member'; +import { ScvdSymbol } from './scvd-symbol'; +import { ScvdVar } from './scvd-var'; +import { getArrayFromJson, getStringFromJson } from './scvd-utils'; + +// Container +export class ScvdTypedefs extends ScvdNode { + private _typedef: ScvdTypedef[] = []; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdTypedefs'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + const typedefs = getArrayFromJson(xml.typedef); + typedefs?.forEach( (v: Json) => { + const varItem = this.addTypedef(); + varItem.readXml(v); + }); + + return super.readXml(xml); + } + + public get typedef(): ScvdTypedef[] { + return this._typedef; + } + + public addTypedef(): ScvdTypedef { + const typedefItem = new ScvdTypedef(this); + this._typedef.push(typedefItem); + return typedefItem; + } + + public async calculateTypedefs(): Promise { + const typedefs = this.typedef; + if (typedefs === undefined || typedefs.length === 0) { + return; + } + + for (const typedef of typedefs) { + await typedef.calculateTypedef(); + } + } + +} + +/* + Member-import (Attribute import="symbol-name" from SCVD xml): + Name of a symbol in the user application which is loaded into the debugger. + The underlaying data type of this symbol is used to + recalculate the value of the attribute size of this typedef element. + For member elements with no explicit attribute offset, the offset value of + matching member is set. If the member is not part of the symbol in the user + application the attribute offset value is set to -1. + __Offset_of can be used to check this value. + + Currently this is not supported due to MS-DAP restrictions. +*/ + +export class ScvdTypedef extends ScvdNode { + private _size: ScvdExpression | undefined; // size is optional and recalculated if import is set + private _import: ScvdSymbol | undefined; + private _member: ScvdMember[] = []; // target system variable + private _var: ScvdVar[] = []; // local SCVD variable + private _virtualSize: number | undefined; + private _targetSize: number | undefined; + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdTypedef'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.size = getStringFromJson(xml.size); + this.import = getStringFromJson(xml.import); + + const members = getArrayFromJson(xml.member); + members?.forEach( (v: Json) => { + const memberItem = this.addMember(); + memberItem.readXml(v); + }); + this._member.sort(this.sortByLine); + + const vars = getArrayFromJson(xml.var); + vars?.forEach( (v: Json) => { + const varItem = this.addVar(); + varItem.readXml(v); + }); + + this._var.sort(this.sortByLine); + + return super.readXml(xml); + } + + public override getTypeSize(): number | undefined { + return this.getTargetSize(); + } + + // 1. Get the virtual size including vars + // 2. If not set, return the size expression value + public override getVirtualSize(): number | undefined { + const virtualSize = this.virtualSize; // calculated size including vars + if (virtualSize !== undefined) { + return virtualSize; + } + return this.getTypeSize(); + } + + public override getIsPointer(): boolean { + return false; + } + + public override getTargetSize(): number | undefined { + return this.targetSize; + } + + + private get targetSize(): number | undefined { + return this._targetSize; + } + private set targetSize(value: number | undefined) { + this._targetSize = value; + } + + public get size(): ScvdExpression | undefined { + return this._size; + } + public set size(value: string | undefined) { + if (value !== undefined) { + this._size = new ScvdExpression(this, value, 'size'); + } + } + + public get virtualSize(): number | undefined { + return this._virtualSize; + } + public set virtualSize(value: number | undefined) { + this._virtualSize = value; + } + + public set import(value: string | undefined) { + if (value !== undefined) { + if ( this._import === undefined) { + this._import = new ScvdSymbol(this, value); + return; + } + this._import.name = value; + } + } + public get import(): ScvdSymbol | undefined { + return this._import; + } + + public addMember(): ScvdMember { + const memberItem = new ScvdMember(this); + this._member.push(memberItem); + return memberItem; + } + public get member(): ScvdMember[] { + return this._member; + } + + public addVar(): ScvdVar { + const varItem = new ScvdVar(this); + this._var.push(varItem); + return varItem; + } + public get var(): ScvdVar[] { + return this._var; + } + + public override getMember(property: string): ScvdNode | undefined { + return this.symbolsCache( + property, + this.member.find(s => s.name === property) ?? + this.var.find(s => s.name === property) + ); + } + + public async calculateTypedef() { + if (this.import !== undefined) { + await this.import.fetchSymbolInformation(); + } + + await this.calculateOffsets(); + } + + private alignToDword(addr: number): number { + return (addr + 3) & ~3; + } + + public async calculateOffsets() { // move to after starting debug session + let currentNextOffset = 0; + for (const member of this._member) { + const memberOffset = member.offset; + if (memberOffset !== undefined) { // ---- offset expression is set ---- + const offsetVal = await memberOffset.getValue(); + if (offsetVal !== undefined) { // TOIMPL: on error?! + const numOffset = Number(offsetVal); + if (numOffset > currentNextOffset) { + currentNextOffset = numOffset; // store offset + } + } + } else { // ---- offset expression is not set ---- + if (this.import !== undefined) { // import from Debugger + const offset = this.import.getOffset(member.name); + if (offset !== undefined) { + member.offset = offset.toString(); + } + } else { + member.offset = currentNextOffset.toString(); // set current offset + console.error(`ScvdTypedef.calculateOffsets: no offset defined for member: ${member.name} in typedef: ${this.getDisplayLabel()}`); + } + member.offset?.configure(); + } + + const memberSize = member.getTypeSize(); + if (memberSize !== undefined) { // TOIMPL: on error?! + currentNextOffset += memberSize; + } + } + + const sizeVal = this.size ? await this.size.getValue() : undefined; + const size = sizeVal !== undefined ? Number(sizeVal) : undefined; + if (size !== undefined) { // if size is defined, use it + this.targetSize = size; + + if (currentNextOffset > size) { + console.error(`ScvdTypedef.calculateOffsets: typedef size (${size}) smaller than members size (${currentNextOffset}) for ${this.getDisplayLabel()}`); + } else if (currentNextOffset < size) { // adjust to typedef size if padding is included + currentNextOffset = size; + } + } else { + this.targetSize = currentNextOffset; + } + + currentNextOffset = this.alignToDword(currentNextOffset + 4); // make sure no overlaps happen when reading target memory + + for (const varItem of this.var) { + const varSize = varItem.getTargetSize() ?? 4; // default size 4 bytes + varItem.offset = currentNextOffset.toString(); // set current offset + varItem.offset?.configure(); + currentNextOffset += varSize; + } + this.virtualSize = currentNextOffset; + } + + +} diff --git a/src/views/component-viewer/model/scvd-utils.ts b/src/views/component-viewer/model/scvd-utils.ts new file mode 100644 index 00000000..518d16c8 --- /dev/null +++ b/src/views/component-viewer/model/scvd-utils.ts @@ -0,0 +1,103 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Json } from './scvd-base'; + + +export function AddText(text: string, addText: string | string[]): string { + if (Array.isArray(addText)) { + if (text.length) { + return text + ' ' + addText.join(' '); + } + return addText.join(' '); + } + + if (text.length) { + return text + ' ' + addText; + } + return addText; +} + +export function insertString(text: string, add: string, pos: number): string { + if (pos === 0) { + return add + text; + } + if (pos >= text.length) { + return text + add; + } + + return text.slice(0, pos) + add + text.slice(pos); +} + +export function unsignedRShift(num: number, rShift: number) { + return num >>> rShift; +} + +export function clearSignBit(num: number) { + return num >>> 0; +} + +export function getObjectFromJson(xml: unknown): T | undefined { + if (xml === undefined || xml === null) { + return undefined; + } + if (typeof xml !== 'object') { + return undefined; + } + return xml as T; +} + +export function getStringFromJson(value: unknown): string | undefined { + return typeof value === 'string' ? value : undefined; +} + +export function getStringField(obj: Json | undefined, key: string): string | undefined { + if (!obj) { + return undefined; + } + // Controlled access into parsed JSON object. + // eslint-disable-next-line security/detect-object-injection + return getStringFromJson(obj[key]); +} + +export function getArrayFromJson(value: unknown): T[] | undefined { + if (value === undefined) { + return undefined; + } + return Array.isArray(value) ? value as T[] : [value as T]; +} + +// Extract raw text body (multiline) from the XML JSON object. +// Depending on the XML-to-JSON converter, text may reside in '#text', '_', or 'text'. +export function getTextBodyFromJson(xml: Json): string[] | undefined { + const raw = typeof xml === 'string' + ? xml + : (xml?.['#text'] ?? xml?._ ?? xml?.text); + const text: string | undefined = typeof raw === 'string' ? raw : undefined; + + if (typeof text === 'string') { + return text + .split(/[;\r\n]+/) + .map(l => l.trim()) + .filter(l => l.length > 0); + } + return undefined; +} + +export function getLineNumberFromJson(xml: Json): string | undefined { + const lineNo = xml?.['__line']; + return typeof lineNo === 'string' ? lineNo : undefined; +} diff --git a/src/views/component-viewer/model/scvd-value-output.ts b/src/views/component-viewer/model/scvd-value-output.ts new file mode 100644 index 00000000..4fafb145 --- /dev/null +++ b/src/views/component-viewer/model/scvd-value-output.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + +import { ScvdNode } from './scvd-node'; +import { ScvdPrintExpression } from './scvd-print-expression'; + +export class ScvdValueOutput extends ScvdNode { + private _expression: ScvdPrintExpression | undefined; + private _scvdVarName: string = 'valueOutput'; + + constructor( + parent: ScvdNode | undefined, + expression: string, + scvdVarName: string, + ) { + super(parent); + this._expression = new ScvdPrintExpression(this, expression, scvdVarName); + this._scvdVarName = scvdVarName; + } + + public override get classname(): string { + return 'ScvdValueOutput'; + } + + public get expression(): ScvdPrintExpression | undefined { + return this._expression; + } + + public set expression(value: string) { + if ( this._expression === undefined) { + this._expression = new ScvdPrintExpression(this, value, this._scvdVarName); + return; + } + this._expression.expression = value; + } + + public override async getGuiName(): Promise { + const expression = this.expression; + if (expression === undefined) { + return undefined; + } + return await expression.getResultString(); + } + + public override async getGuiValue(): Promise { + const expression = this.expression; + if (expression === undefined) { + return undefined; + } + return await expression.getResultString(); + } +} diff --git a/src/views/component-viewer/model/scvd-var.ts b/src/views/component-viewer/model/scvd-var.ts new file mode 100644 index 00000000..ef9b463f --- /dev/null +++ b/src/views/component-viewer/model/scvd-var.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// https://arm-software.github.io/CMSIS-View/main/elem_var.html + +import { ScvdDataType } from './scvd-data-type'; +import { ScvdExpression } from './scvd-expression'; +import { Json } from './scvd-base'; +import { ScvdNode } from './scvd-node'; +import { getStringFromJson } from './scvd-utils'; +import { NumberType, NumberTypeInput } from './number-type'; + +export class ScvdVar extends ScvdNode { + private _value: ScvdExpression | undefined; + private _type: ScvdDataType | undefined; + private _offset: ScvdExpression | undefined; + private _size: number | undefined; + + + constructor( + parent: ScvdNode | undefined, + ) { + super(parent); + } + + public override get classname(): string { + return 'ScvdVar'; + } + + public override readXml(xml: Json): boolean { + if (xml === undefined ) { + return super.readXml(xml); + } + + this.value = getStringFromJson(xml.value); + this.type = getStringFromJson(xml.type); + this.size = getStringFromJson(xml.size); + + return super.readXml(xml); + } + + public get value(): ScvdExpression | undefined { + return this._value; + } + public set value(value: string | undefined) { + if (value !== undefined) { + this._value = new ScvdExpression(this, value, 'value'); + } + } + + public get size(): number | undefined { + return this._size; + } + + public set size(value: NumberTypeInput | undefined) { + if (value !== undefined) { + this._size = new NumberType(value).value; + } + } + + public override async getValue(): Promise { + if (this._value === undefined) { + return undefined; + } + const val = await this._value.getValue(); + if (typeof val === 'number' || typeof val === 'bigint') { + return val; + } + return undefined; + } + + public get type(): ScvdDataType | undefined { + return this._type; + } + public set type(value: string | undefined) { + if (value !== undefined) { + if ( this._type === undefined) { + this._type = new ScvdDataType(this, value); + return; + } + this._type.type = value; + } + } + + public override getTypeSize(): number | undefined { + return this.type?.getTypeSize(); + } + + public override getVirtualSize(): number | undefined { + return this.getTargetSize(); + } + + // TOIMPL: total size in bytes or type size? + public override getTargetSize(): number | undefined { + const typeSize = this.getTypeSize(); + const elements = this.size ?? 1; + if ( typeSize !== undefined) { + return typeSize * elements; + } + return elements; + } + + public override getIsPointer(): boolean { + return this.type?.getIsPointer() ?? false; + } + + public get offset(): ScvdExpression | undefined { + return this._offset; + } + + public set offset(value: string | undefined) { + if (value !== undefined) { + this._offset = new ScvdExpression(this, value, 'offset'); + } + } + + // member’s byte offset + public override async getMemberOffset(): Promise { + const offsetExpr = this._offset; + if (offsetExpr !== undefined) { + const offsetValue = await offsetExpr.getValue(); + if (typeof offsetValue === 'number') { + return offsetValue; + } + } + return 0; // TOIMPL: default? + } + + + // search a member (member, var) in typedef + public override getMember(_property: string): ScvdNode | undefined { + const type = this._type; + if (type !== undefined) { + const typeObj = type.getMember(_property); + return typeObj; + } + return undefined; + } + + public override getElementRef(): ScvdNode | undefined { + const typeObj = this._type; + if (typeObj !== undefined) { + return typeObj; + } + return undefined; + } + + public override getValueType(): string | undefined { + return this.type?.getValueType(); + } + +} diff --git a/src/views/component-viewer/parser-evaluator/evaluator.ts b/src/views/component-viewer/parser-evaluator/evaluator.ts new file mode 100644 index 00000000..a453e988 --- /dev/null +++ b/src/views/component-viewer/parser-evaluator/evaluator.ts @@ -0,0 +1,1074 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import type { + ASTNode, + NumberLiteral, + StringLiteral, + BooleanLiteral, + Identifier, + MemberAccess, + ArrayIndex, + UnaryExpression, + BinaryExpression, + ConditionalExpression, + AssignmentExpression, + UpdateExpression, + CallExpression, + EvalPointCall, + PrintfExpression, + FormatSegment, + TextSegment, + ParseResult, + ColonPath, +} from './parser'; +import type { ScvdNode } from '../model/scvd-node'; +import { + addVals, + andVals, + divVals, + mergeKinds as mergeScalarKinds, + modVals, + mulVals, + normalizeToWidth, + orVals, + sarVals, + shlVals, + subVals, + toBigInt, + toNumeric, + xorVals, +} from './math-ops'; +import type { DataAccessHost, EvalValue, ModelHost, RefContainer, ScalarKind, ScalarType } from './model-host'; +import type { IntrinsicProvider } from './intrinsics'; +import { handleIntrinsic, handlePseudoMember, INTRINSIC_DEFINITIONS, IntrinsicName, isIntrinsicName } from './intrinsics'; + +/* ============================================================================= + * Public API + * ============================================================================= */ + +export type EvaluateResult = number | string | bigint | Uint8Array | undefined; + +type Host = ModelHost & DataAccessHost & IntrinsicProvider; + +export interface EvalContextInit { + data: Host; + // Starting container for symbol resolution (root model). + container: ScvdNode; +} + +export class EvalContext { + readonly data: Host; + // Composite container context (root + last member/index/current). + container: RefContainer; + + constructor(init: EvalContextInit) { + this.data = init.data; + this.container = { + base: init.container, + valueType: undefined, + }; + } +} + +/* ============================================================================= + * Helpers + * ============================================================================= */ + +function snapshotContainer(container: RefContainer): RefContainer { + return { ...container }; +} + +function isReferenceNode(node: ASTNode): node is Identifier | MemberAccess | ArrayIndex { + return node.kind === 'Identifier' || node.kind === 'MemberAccess' || node.kind === 'ArrayIndex'; +} + +function findReferenceNode(node: ASTNode | undefined): Identifier | MemberAccess | ArrayIndex | undefined { + if (!node) { + return undefined; + } + if (isReferenceNode(node)) { + return node; + } + + switch (node.kind) { + case 'UnaryExpression': return findReferenceNode((node as UnaryExpression).argument); + case 'UpdateExpression': return findReferenceNode((node as UpdateExpression).argument); + case 'BinaryExpression': { + const b = node as BinaryExpression; + return findReferenceNode(b.right) ?? findReferenceNode(b.left); + } + case 'ConditionalExpression': { + const c = node as ConditionalExpression; + return findReferenceNode(c.test) ?? findReferenceNode(c.consequent) ?? findReferenceNode(c.alternate); + } + case 'AssignmentExpression': { + const a = node as AssignmentExpression; + return findReferenceNode(a.right) ?? findReferenceNode(a.left); + } + case 'CallExpression': { + const c = node as CallExpression; + for (const arg of [...c.args].reverse()) { + const r = findReferenceNode(arg); + if (r) { + return r; + } + } + return findReferenceNode(c.callee); + } + case 'EvalPointCall': { + const c = node as EvalPointCall; + for (const arg of [...c.args].reverse()) { + const r = findReferenceNode(arg); + if (r) { + return r; + } + } + return findReferenceNode(c.callee); + } + case 'PrintfExpression': { + for (const seg of (node as PrintfExpression).segments) { + if (seg.kind === 'FormatSegment') { + const r = findReferenceNode(seg.value); + if (r) { + return r; + } + } + } + return undefined; + } + default: + return undefined; + } +} + +async function captureContainerForReference(node: ASTNode, ctx: EvalContext): Promise { + if (!isReferenceNode(node)) { + return undefined; + } + + let captured: RefContainer | undefined; + await withIsolatedContainer(ctx, async () => { + await mustRef(node, ctx, false); + captured = snapshotContainer(ctx.container); + }); + return captured; +} + +function truthy(x: unknown): boolean { + return !!x; +} + +function asNumber(x: unknown): number { + if (typeof x === 'number') { + return Number.isFinite(x) ? x : 0; + } + if (typeof x === 'bigint') { + return Number(x); + } + if (typeof x === 'boolean') { + return x ? 1 : 0; + } + if (typeof x === 'string' && x.trim() !== '') { + const n = +x; + return Number.isFinite(n) ? n : 0; + } + return 0; +} + +function eqVals(a: EvalValue, b: EvalValue): boolean { + if (typeof a === 'string' || typeof b === 'string') { + return String(a) === String(b); + } + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na as EvalValue) === toBigInt(nb as EvalValue); + } + return (na as number) == (nb as number); +} +function ltVals(a: EvalValue, b: EvalValue): boolean { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na as EvalValue) < toBigInt(nb as EvalValue); + } + return (na as number) < (nb as number); +} +function lteVals(a: EvalValue, b: EvalValue): boolean { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na as EvalValue) <= toBigInt(nb as EvalValue); + } + return (na as number) <= (nb as number); +} +function gtVals(a: EvalValue, b: EvalValue): boolean { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na as EvalValue) > toBigInt(nb as EvalValue); + } + return (na as number) > (nb as number); +} +function gteVals(a: EvalValue, b: EvalValue): boolean { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na as EvalValue) >= toBigInt(nb as EvalValue); + } + return (na as number) >= (nb as number); +} + +/* ============================================================================= + * Type helpers and typed arithmetic decisions + * ============================================================================= */ + +type MergedKind = ScalarKind | 'unknown'; +const mergeKinds = mergeScalarKinds; + +function normalizeScalarTypeFromName(name: string): ScalarType { + const trimmed = name.trim(); + const lower = trimmed.toLowerCase(); + + let kind: ScalarKind = 'int'; + if (lower.includes('uint') || lower.includes('unsigned')) { + kind = 'uint'; + } else if (lower.includes('float') || lower.includes('double')) { + kind = 'float'; + } + + const out: ScalarType = { kind, name: trimmed }; + + const m = lower.match(/(8|16|32|64)/); + if (m) { + out.bits = parseInt(m[1], 10); + } + + return out; +} + +function normalizeScalarType(t: string | ScalarType | undefined): ScalarType | undefined { + if (!t) { + return undefined; + } + if (typeof t === 'string') { + return normalizeScalarTypeFromName(t); + } + if (!t.name && t.typename) { + t.name = t.typename; + } + return t; +} + +async function getScalarTypeForContainer(ctx: EvalContext, container: RefContainer): Promise { + const fn = ctx.data.getValueType; + if (typeof fn !== 'function') { + return undefined; + } + const raw = await fn.call(ctx.data, container); + return normalizeScalarType(raw); +} + +function integerDiv(a: number | bigint, b: number | bigint, unsigned: boolean): number | bigint { + if ((typeof b === 'bigint' && b === 0n) || (typeof b === 'number' && b === 0)) { + throw new Error('Division by zero'); + } + if (typeof a === 'bigint' || typeof b === 'bigint') { + const na = toBigInt(a as EvalValue); + const nb = toBigInt(b as EvalValue); + if (nb === 0n) { + throw new Error('Division by zero'); + } + // unsigned is ignored for bigint (values already exact) + return na / nb; + } + if (unsigned) { + const na = (a as number) >>> 0; + const nb = (b as number) >>> 0; + if (nb === 0) { + throw new Error('Division by zero'); + } + return Math.trunc(na / nb) >>> 0; + } else { + const na = (a as number) | 0; + const nb = (b as number) | 0; + if (nb === 0) { + throw new Error('Division by zero'); + } + return (na / nb) | 0; + } +} + +function integerMod(a: number | bigint, b: number | bigint, unsigned: boolean): number | bigint { + if ((typeof b === 'bigint' && b === 0n) || (typeof b === 'number' && b === 0)) { + throw new Error('Division by zero'); + } + if (typeof a === 'bigint' || typeof b === 'bigint') { + const na = toBigInt(a as EvalValue); + const nb = toBigInt(b as EvalValue); + if (nb === 0n) { + throw new Error('Division by zero'); + } + return na % nb; + } + if (unsigned) { + const na = (a as number) >>> 0; + const nb = (b as number) >>> 0; + if (nb === 0) { + throw new Error('Division by zero'); + } + return (na % nb) >>> 0; + } else { + const na = (a as number) | 0; + const nb = (b as number) | 0; + if (nb === 0) { + throw new Error('Division by zero'); + } + return na % nb; + } +} + +/** + * Decide whether to prefer integer semantics for a/b based on: + * - an explicit merged kind from type info, OR + * - a fallback heuristic: both operands are integer-valued numbers. + */ +function prefersInteger(kind: MergedKind | undefined, a: EvalValue, b: EvalValue): { use: boolean; unsigned: boolean } { + if (kind === 'int') { + return { use: true, unsigned: false }; + } + if (kind === 'uint') { + return { use: true, unsigned: true }; + } + + // Fallback when host doesn't provide types: + const na = toNumeric(a); + const nb = toNumeric(b); + if ((typeof na === 'bigint') || (typeof nb === 'bigint') || (Number.isInteger(na as number) && Number.isInteger(nb as number))) { + // Default to signed if we only know "integer-ish" + return { use: true, unsigned: false }; + } + return { use: false, unsigned: false }; +} + +function divValsWithKind(a: EvalValue, b: EvalValue, kind: MergedKind | undefined): EvalValue { + const pref = prefersInteger(kind, a, b); + if (pref.use) { + return integerDiv(toNumeric(a), toNumeric(b), pref.unsigned); + } + // Fallback to original floating semantics + return divVals(a, b); +} + +function modValsWithKind(a: EvalValue, b: EvalValue, kind: MergedKind | undefined): EvalValue { + const pref = prefersInteger(kind, a, b); + if (pref.use) { + return integerMod(toNumeric(a), toNumeric(b), pref.unsigned); + } + return modVals(a, b); +} + +async function evalArgsForIntrinsic(name: IntrinsicName, rawArgs: ASTNode[], ctx: EvalContext): Promise { + // INTRINSIC_DEFINITIONS is a trusted static map. + // eslint-disable-next-line security/detect-object-injection + const needsName = INTRINSIC_DEFINITIONS[name]?.expectsNameArg === true; + + const resolved: EvalValue[] = []; + for (const [idx, arg] of rawArgs.entries()) { + if (!needsName) { + resolved.push(await evalNode(arg, ctx)); + continue; + } + + // For name-based intrinsics, allow Identifier or "string literal" + if (arg.kind === 'Identifier') { + resolved.push((arg as Identifier).name); + continue; + } + if (arg.kind === 'StringLiteral') { + resolved.push((arg as StringLiteral).value); + continue; + } + // Make the failure explicit; this avoids silently passing evaluated values like 0. + throw new Error(`${name} expects an identifier or string literal for argument ${idx + 1}`); + } + + return resolved; +} + +/* ============================================================================= + * Small utility to avoid container clobbering during nested evals + * ============================================================================= */ + +async function withIsolatedContainer(ctx: EvalContext, fn: () => Promise): Promise { + const c = ctx.container; + const saved = snapshotContainer(c); + try { + return await fn(); + } finally { + c.anchor = saved.anchor; + c.offsetBytes = saved.offsetBytes; + c.widthBytes = saved.widthBytes; + c.current = saved.current; + c.member = saved.member; + c.index = saved.index; + c.valueType = saved.valueType; + } +} + +/* ============================================================================= + * Strict ref/value utilities (single-root + contextual hints) + * ============================================================================= */ + +type LValue = { + get(): Promise; + set(v: EvalValue): Promise; + type: ScalarType | undefined; +}; + +// Accumulate a byte offset into the container (anchor-relative). +function addByteOffset(ctx: EvalContext, bytes: number) { + const c = ctx.container; + const add = (bytes | 0); + c.offsetBytes = ((c.offsetBytes ?? 0) + add); +} + +async function mustRef(node: ASTNode, ctx: EvalContext, forWrite = false): Promise { + switch (node.kind) { + case 'Identifier': { + const id = node as Identifier; + // Identifier lookup always starts from the root base + const ref = await ctx.data.getSymbolRef(ctx.container, id.name, forWrite); + if (!ref) { + throw new Error(`Unknown symbol '${id.name}'`); + } + // Start a new anchor chain at this identifier + ctx.container.anchor = ref; + ctx.container.offsetBytes = 0; + ctx.container.widthBytes = undefined; + // Reset last-context hints for a plain identifier + ctx.container.member = undefined; + ctx.container.index = undefined; + ctx.container.valueType = undefined; + // Set the current target for subsequent resolution + ctx.container.current = ref; + + // Prefer a byte-based width helper if host provides one + const byteWidthFn = ctx.data.getByteWidth; + if (typeof byteWidthFn === 'function') { + const w = await byteWidthFn.call(ctx.data, ref); + if (typeof w === 'number' && w > 0) { + ctx.container.widthBytes = w; + } + } + + return ref; + } + + case 'MemberAccess': { + const ma = node as MemberAccess; + + // Fast-path: if object is an ArrayIndex, compute index ONCE, then resolve the member on the element. + if (ma.object.kind === 'ArrayIndex') { + const ai = ma.object as ArrayIndex; + + // Resolve array symbol and establish anchor/current + const baseRef = await mustRef(ai.array, ctx, forWrite); + + // Evaluate index in isolation (so i/j/mem.length don't clobber outer anchor) + const idx = asNumber(await withIsolatedContainer(ctx, () => evalNode(ai.index, ctx))) | 0; + + // Remember the index for hosts that use it + ctx.container.index = idx; + + // Use the thing we're actually indexing (supports nested arr[i][j].field) + const arrayRef = ctx.container.current ?? baseRef; + + // Apply array offset using the correct dimension's stride (bytes) + const strideBytes = ctx.data.getElementStride ? await ctx.data.getElementStride(arrayRef) : 0; + if (typeof strideBytes === 'number' && strideBytes !== 0) { + addByteOffset(ctx, idx * strideBytes); + } + + // Base for member resolution = element model if host provides one + const baseForMember = ctx.data.getElementRef ? (await ctx.data.getElementRef(arrayRef)) ?? arrayRef : arrayRef; + ctx.container.current = baseForMember; + + // Resolve member + const child = await ctx.data.getMemberRef(ctx.container, ma.property, forWrite); + if (!child) { + throw new Error(`Missing member '${ma.property}'`); + } + + // Accumulate member byte offset + const memberOffsetBytes = ctx.data.getMemberOffset ? await ctx.data.getMemberOffset(baseForMember, child) : undefined; + if (typeof memberOffsetBytes === 'number') { + addByteOffset(ctx, memberOffsetBytes); + } + + // Width: prefer host byte-width helper if present + const byteWidthFn = ctx.data.getByteWidth; + if (typeof byteWidthFn === 'function') { + const w = await byteWidthFn.call(ctx.data, child); + if (typeof w === 'number' && w > 0) { + ctx.container.widthBytes = w; + } + } + + // Finalize hints + ctx.container.member = child; + ctx.container.current = child; + ctx.container.valueType = undefined; // will be resolved on read/write via getValueType + return child; + } + + // Default path: resolve base then member + const baseRef = await mustRef(ma.object, ctx, forWrite); + + ctx.container.current = baseRef; + const child = await ctx.data.getMemberRef(ctx.container, ma.property, forWrite); + if (!child) { + throw new Error(`Missing member '${ma.property}'`); + } + + const memberOffsetBytes = ctx.data.getMemberOffset ? await ctx.data.getMemberOffset(baseRef, child) : undefined; + if (typeof memberOffsetBytes === 'number') { + addByteOffset(ctx, memberOffsetBytes); + } + + // Width: prefer host byte-width helper if present + const byteWidthFn = ctx.data.getByteWidth; + if (typeof byteWidthFn === 'function') { + const w = await byteWidthFn.call(ctx.data, child); + if (typeof w === 'number' && w > 0) { + ctx.container.widthBytes = w; + } + } + + ctx.container.member = child; + ctx.container.current = child; + ctx.container.valueType = undefined; + return child; + } + + case 'ArrayIndex': { + const ai = node as ArrayIndex; + + // Resolve array base (establishes anchor/current on the array) + const baseRef = await mustRef(ai.array, ctx, forWrite); + + // Evaluate the index in isolation + const idx = asNumber(await withIsolatedContainer(ctx, () => evalNode(ai.index, ctx))) | 0; + + // Translate index -> byte offset + ctx.container.index = idx; + + const arrayRef = ctx.container.current ?? baseRef; + ctx.container.member = undefined; + ctx.container.valueType = undefined; + + const strideBytes = ctx.data.getElementStride ? await ctx.data.getElementStride(arrayRef) : 0; + if (typeof strideBytes === 'number' && strideBytes !== 0) { + addByteOffset(ctx, idx * strideBytes); + } + + // Current target becomes element if host exposes it, otherwise array + const elementRef = ctx.data.getElementRef ? (await ctx.data.getElementRef(arrayRef)) ?? arrayRef : arrayRef; + ctx.container.current = elementRef; + + // Update width to element width if host exposes a byte-width helper + const byteWidthFn = ctx.data.getByteWidth; + if (typeof byteWidthFn === 'function') { + const w = await byteWidthFn.call(ctx.data, elementRef); + if (typeof w === 'number' && w > 0) { + ctx.container.widthBytes = w; + } + } + + return baseRef; + } + + case 'EvalPointCall': { + throw new Error('Invalid reference target.'); + } + + default: + throw new Error('Invalid reference target.'); + } +} + +async function mustRead(ctx: EvalContext, label?: string): Promise { + // ensure hosts know the expected scalar type for decoding (e.g., float vs int) + if (ctx.container.valueType === undefined) { + ctx.container.valueType = await getScalarTypeForContainer(ctx, ctx.container); + } + const v = await ctx.data.readValue(ctx.container); + if (v === undefined) { + throw new Error(label ? `Undefined value for ${label}` : 'Undefined value'); + } + return v; +} + +async function lref(node: ASTNode, ctx: EvalContext): Promise { + // Resolve and set the current target in the container for writes + await mustRef(node, ctx, true); + + // Snapshot the LHS write target so RHS evaluation can't clobber it + const target = snapshotContainer(ctx.container); + + const valueType = await getScalarTypeForContainer(ctx, target); + + const lv: LValue = { + async get(): Promise { + await mustRef(node, ctx, false); + ctx.container.valueType = valueType; + return await mustRead(ctx); + }, + async set(v: EvalValue): Promise { + const out = await ctx.data.writeValue(target, v); // use frozen target + if (out === undefined) { + throw new Error('Write returned undefined'); + } + return out; + }, + type: valueType, + }; + + return lv; +} + +/* ============================================================================= + * Evaluation + * ============================================================================= */ + +async function evalOperandWithType(node: ASTNode, ctx: EvalContext): Promise<{ value: EvalValue; type: ScalarType | undefined }> { + let capturedType: ScalarType | undefined; + + const value = await withIsolatedContainer(ctx, async () => { + const v = await evalNode(node, ctx); + + const snapshot = snapshotContainer(ctx.container); + + capturedType = await getScalarTypeForContainer(ctx, snapshot); + return v; + }); + + return { value, type: capturedType }; +} + +export async function evalNode(node: ASTNode, ctx: EvalContext): Promise { + switch (node.kind) { + case 'NumberLiteral': return (node as NumberLiteral).value; + case 'StringLiteral': return (node as StringLiteral).value; + case 'BooleanLiteral': return (node as BooleanLiteral).value; + + case 'Identifier': { + const name = (node as Identifier).name; + // __Running can appear as a bare identifier; treat it as an intrinsic, not a symbol. + if (name === '__Running') { + return await handleIntrinsic(ctx.data, ctx.container, '__Running', []); + } + await mustRef(node, ctx, false); + return await mustRead(ctx, name); + } + + case 'MemberAccess': { + const ma = node as MemberAccess; + // Support pseudo-members that evaluate to numbers: obj._count and obj._addr + if (ma.property === '_count' || ma.property === '_addr') { + const baseRef = await mustRef(ma.object, ctx, false); + return await handlePseudoMember(ctx.data, ctx.container, ma.property, baseRef); + } + // Default: resolve member and read its value + await mustRef(node, ctx, false); + return await mustRead(ctx); + } + + case 'ArrayIndex': { + await mustRef(node, ctx, false); + return await mustRead(ctx); + } + + case 'ColonPath': { + const cp = node as ColonPath; + // Colon paths (foo:bar:baz) are host-defined lookups resolved by the DataHost. + const handled = ctx.data.resolveColonPath + ? await ctx.data.resolveColonPath(ctx.container, cp.parts.slice()) + : undefined; + if (handled === undefined) { + throw new Error(`Unresolved colon path: ${cp.parts.join(':')}`); + } + return handled; + } + + case 'UnaryExpression': { + const u = node as UnaryExpression; + const v = await evalNode(u.argument, ctx); + switch (u.operator) { + case '+': { + const n = toNumeric(v); + return typeof n === 'bigint' ? n : +n; + } + case '-': { + const n = toNumeric(v); + return typeof n === 'bigint' ? -toBigInt(n as EvalValue) : -asNumber(n); + } + case '!': return !truthy(v); + case '~': { + const n = toNumeric(v); + if (typeof n === 'bigint') { + return ~n; + } + return ((~(asNumber(n) | 0)) >>> 0); + } + default: throw new Error(`Unsupported unary operator ${u.operator}`); + } + } + + case 'UpdateExpression': { + const u = node as UpdateExpression; + const ref = await lref(u.argument, ctx); + const prev = await ref.get(); + const next = (u.operator === '++' + ? (typeof prev === 'bigint' ? prev + 1n : asNumber(prev) + 1) + : (typeof prev === 'bigint' ? prev - 1n : asNumber(prev) - 1)); + await ref.set(next); + return u.prefix ? ref.get() : prev; + } + + case 'BinaryExpression': return await evalBinary(node as BinaryExpression, ctx); + + case 'ConditionalExpression': { + const c = node as ConditionalExpression; + return truthy(await evalNode(c.test, ctx)) + ? await evalNode(c.consequent, ctx) + : await evalNode(c.alternate, ctx); + } + + case 'AssignmentExpression': { + const a = node as AssignmentExpression; + const ref = await lref(a.left, ctx); + if (a.operator === '=') { + const value = await withIsolatedContainer(ctx, () => evalNode(a.right, ctx)); + return await ref.set(value); + } + + // Use the LValue to read current LHS value (and we already captured its type in lref) + const L = await ref.get(); + const R = await evalNode(a.right, ctx); + const lhsKind: MergedKind = ref.type ? ref.type.kind : 'unknown'; + + let out: EvalValue; + switch (a.operator) { + case '+=': out = addVals(L, R); break; + case '-=': out = subVals(L, R); break; + case '*=': out = mulVals(L, R); break; + case '/=': out = divValsWithKind(L, R, lhsKind); break; + case '%=': out = modValsWithKind(L, R, lhsKind); break; + case '<<=': out = shlVals(L, R); break; + case '>>=': out = sarVals(L, R); break; + case '&=': out = andVals(L, R); break; + case '^=': out = xorVals(L, R); break; + case '|=': out = orVals(L, R); break; + default: throw new Error(`Unsupported assignment operator ${a.operator}`); + } + return await ref.set(out); + } + + case 'CallExpression': { + const c = node as CallExpression; + + if (c.callee.kind === 'Identifier') { + const name = (c.callee as Identifier).name; + if (isIntrinsicName(name) && ( + // eslint-disable-next-line security/detect-object-injection + INTRINSIC_DEFINITIONS[name].allowCallExpression + )) { + const args = await evalArgsForIntrinsic(name, c.args, ctx); + return await handleIntrinsic(ctx.data, ctx.container, name, args); + } + } + + const args = []; + for (const a of c.args) { + args.push(await evalNode(a, ctx)); // evaluate sequentially to avoid parallel side effects + } + const fnVal = await evalNode(c.callee, ctx); + if (typeof fnVal === 'function') { + return await fnVal(...args); + } + throw new Error('Callee is not callable.'); + } + + case 'EvalPointCall': { + const c = node as EvalPointCall; + const name = c.intrinsic as string; + if (!isIntrinsicName(name)) { + throw new Error(`Missing intrinsic ${name}`); + } + const intrinsicName = name as IntrinsicName; + const args = await evalArgsForIntrinsic(intrinsicName, c.args, ctx); + return await handleIntrinsic(ctx.data, ctx.container, intrinsicName, args); + } + + case 'PrintfExpression': { + const pf = node as PrintfExpression; + let out = ''; + for (const seg of pf.segments) { + if (seg.kind === 'TextSegment') { + out += (seg as TextSegment).text; + } else { + const fs = seg as FormatSegment; + const { value, container } = await evaluateFormatSegmentValue(fs, ctx); + out += await formatValue(fs.spec, value, ctx, container); + } + } + return out; + } + + case 'TextSegment': return (node as TextSegment).text; + case 'FormatSegment': { + const seg = node as FormatSegment; + const { value, container } = await evaluateFormatSegmentValue(seg, ctx); + return await formatValue(seg.spec, value, ctx, container); + } + + case 'ErrorNode': throw new Error('Cannot evaluate an ErrorNode.'); + + default: { + const kind = (node as Partial).kind ?? 'unknown'; + throw new Error(`Unhandled node kind: ${kind}`); + } + } +} + +async function evalBinary(node: BinaryExpression, ctx: EvalContext): Promise { + const { operator, left, right } = node; + if (operator === '&&') { + const lv = await evalNode(left, ctx); + return truthy(lv) ? await evalNode(right, ctx) : lv; + } + if (operator === '||') { + const lv = await evalNode(left, ctx); + return truthy(lv) ? lv : await evalNode(right, ctx); + } + + const { value: a, type: typeA } = await evalOperandWithType(left, ctx); + const { value: b, type: typeB } = await evalOperandWithType(right, ctx); + const mergedKind = mergeKinds(typeA, typeB); + const bitWidthValue = Math.max(typeA?.bits ?? 0, typeB?.bits ?? 0); + const bitWidth = bitWidthValue > 0 ? bitWidthValue : undefined; + + const isUnsigned = mergedKind === 'uint'; + + let result: EvalValue; + + switch (operator) { + case '+': + result = addVals(a, b, bitWidth, isUnsigned); + break; + case '-': + result = subVals(a, b, bitWidth, isUnsigned); + break; + case '*': + result = mulVals(a, b, bitWidth, isUnsigned); + break; + case '/': + result = divValsWithKind(a, b, mergedKind); + break; + case '%': + result = modValsWithKind(a, b, mergedKind); + break; + case '<<': + result = shlVals(a, b, bitWidth, isUnsigned); + break; + case '>>': + result = sarVals(a, b, bitWidth, isUnsigned); + break; + case '>>>': + throw new Error('Unsupported operator >>> in C-style expressions'); + case '&': + result = andVals(a, b, bitWidth, isUnsigned); + break; + case '^': + result = xorVals(a, b, bitWidth, isUnsigned); + break; + case '|': + result = orVals(a, b, bitWidth, isUnsigned); + break; + case '==': { + return eqVals(a, b); + } + case '!=': { + return !eqVals(a, b); + } + case '<': { + return ltVals(a, b); + } + case '<=': { + return lteVals(a, b); + } + case '>': { + return gtVals(a, b); + } + case '>=': { + return gteVals(a, b); + } + default: { + throw new Error(`Unsupported binary operator ${operator}`); + } + } + + if (typeof result === 'number' || typeof result === 'bigint') { + return normalizeToWidth(result, bitWidth, mergedKind); + } + return result; +} + +/* ============================================================================= + * Printf helpers (callback-first, spec-agnostic with sensible fallbacks) + * ============================================================================= */ + +async function evaluateFormatSegmentValue(segment: FormatSegment, ctx: EvalContext): Promise<{ value: EvalValue; container: RefContainer | undefined }> { + const value = await evalNode(segment.value, ctx); + let containerSnapshot = snapshotContainer(ctx.container); + if (!containerSnapshot.current) { + const hasConst = (segment.value as Partial<{ constValue: unknown }>).constValue !== undefined; + if (!hasConst) { + const refNode = findReferenceNode(segment.value); + const recovered = refNode ? await captureContainerForReference(refNode, ctx) : undefined; + if (recovered) { + containerSnapshot = recovered; + } + } + } + return { value, container: containerSnapshot }; +} + +async function formatValue(spec: FormatSegment['spec'], v: EvalValue, ctx?: EvalContext, containerOverride?: RefContainer): Promise { + const formattingContainer = containerOverride ?? ctx?.container; + // New: host-provided override + if (ctx?.data.formatPrintf && formattingContainer) { + const override = await ctx.data.formatPrintf(spec, v, formattingContainer); + if (typeof override === 'string') { + return override; + } + } + + // Existing fallback behaviour + switch (spec) { + case '%': return '%'; + case 'd': { + const n = toNumeric(v); + if (typeof n === 'bigint') { + return n.toString(10); + } + const num = Number(n); + if (!Number.isFinite(num)) { + return 'NaN'; + } + return String((num | 0)); + } + case 'u': { + const n = toNumeric(v); + if (typeof n === 'bigint') { + return (n >= 0 ? n : -n).toString(10); + } + const num = Number(n); + if (!Number.isFinite(num)) { + return 'NaN'; + } + return String((num >>> 0)); + } + case 'x': { + const n = toNumeric(v); + if (typeof n === 'bigint') { + return '0x' + n.toString(16); + } + const num = Number(n); + if (!Number.isFinite(num)) { + return 'NaN'; + } + return (num >>> 0).toString(16); + } + case 't': return truthy(v) ? 'true' : 'false'; + case 'S': return typeof v === 'string' ? v : String(v); + case 'C': case 'E': case 'I': case 'J': case 'N': case 'M': case 'T': case 'U': return String(v); + default: return String(v); + } +} + +function normalizeEvaluateResult(v: EvalValue): EvaluateResult { + if (v === undefined || v === null) { + return undefined; + } + if (typeof v === 'number' || typeof v === 'string') { + return v; + } + if (typeof v === 'boolean') { + return v ? 1 : 0; + } + return undefined; +} + +export async function evaluateParseResult(pr: ParseResult, ctx: EvalContext, container?: ScvdNode): Promise { + const prevBase = ctx.container.base; + const saved = snapshotContainer(ctx.container); + const override = container !== undefined; + if (override) { + ctx.container.base = container as ScvdNode; + } + try { + const v = await evalNode(pr.ast, ctx); + return normalizeEvaluateResult(v); + } catch (e) { + console.error('Error evaluating parse result:', pr, e); + return undefined; + } finally { + if (override) { + ctx.container.base = prevBase; + } + ctx.container.anchor = saved.anchor; + ctx.container.offsetBytes = saved.offsetBytes; + ctx.container.widthBytes = saved.widthBytes; + ctx.container.current = saved.current; + ctx.container.member = saved.member; + ctx.container.index = saved.index; + ctx.container.valueType = saved.valueType; + } +} + +// Test-only access to internal helpers. +export const __test__ = { + findReferenceNode, + asNumber, + integerDiv, + integerMod, + evalArgsForIntrinsic, + mustRef, + formatValue, + eqVals, + ltVals, + lteVals, + gtVals, + gteVals, + getScalarTypeForContainer, + captureContainerForReference, + evalBinary, + normalizeEvaluateResult, +}; diff --git a/src/views/component-viewer/parser-evaluator/intrinsics.ts b/src/views/component-viewer/parser-evaluator/intrinsics.ts new file mode 100644 index 00000000..f9676523 --- /dev/null +++ b/src/views/component-viewer/parser-evaluator/intrinsics.ts @@ -0,0 +1,211 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import type { EvalValue, RefContainer } from './model-host'; +import type { ScvdNode } from '../model/scvd-node'; + +export interface IntrinsicDefinition { + // Arguments should be identifier names (not evaluated values). + expectsNameArg?: boolean; + // Allow CallExpression(Identifier(...)) as a fallback to EvalPointCall. + allowCallExpression?: boolean; + // Minimum positional arguments expected. + minArgs?: number; + // Maximum positional arguments expected. + maxArgs?: number; +} + +// formatPrintf is a host hook, not an intrinsic. Exclude it from intrinsic names. +export type IntrinsicName = Exclude; + +// Intrinsic hooks exposed by the host (built-ins plus pseudo-members). +export interface IntrinsicProvider { + // Named intrinsics + // Note: __GetRegVal(reg) is special-cased (no container); others use the explicit hooks below + __GetRegVal(reg: string): Promise; + __FindSymbol(symbol: string): Promise; + __CalcMemUsed(stackAddress: number, stackSize: number, fillPattern: number, magicValue: number): Promise; + + // sizeof-like intrinsic – semantics are host-defined (usually bytes). + __size_of(symbol: string): Promise; + + __Symbol_exists(symbol: string): Promise; + __Offset_of(container: RefContainer, typedefMember: string): Promise; + + // Additional named intrinsics + // __Running is special-cased (no container) and returns 1 or 0 for use in expressions + __Running(): Promise; + + // Pseudo-member evaluators used as obj._count / obj._addr; must return numbers + _count(container: RefContainer): Promise; + _addr(container: RefContainer): Promise; // added as var because arrays can have different base addresses +} + +// Intrinsics that expect identifier *names* instead of evaluated values. +/** + * Metadata describing special intrinsic handling. Used both by evaluator logic + * and as the single source of truth for intrinsic names in type definitions. + */ +export const INTRINSIC_DEFINITIONS: Record = { + __size_of: { expectsNameArg: true, allowCallExpression: true, minArgs: 1, maxArgs: 1 }, + __FindSymbol: { expectsNameArg: true, allowCallExpression: true, minArgs: 1, maxArgs: 1 }, + __Symbol_exists: { expectsNameArg: true, allowCallExpression: true, minArgs: 1, maxArgs: 1 }, + __GetRegVal: { expectsNameArg: true, allowCallExpression: true, minArgs: 1, maxArgs: 1 }, + __Offset_of: { expectsNameArg: true, allowCallExpression: true, minArgs: 1, maxArgs: 1 }, + __Running: { allowCallExpression: true, minArgs: 0, maxArgs: 0 }, + __CalcMemUsed: { allowCallExpression: true, minArgs: 4, maxArgs: 4 }, + // Included to keep IntrinsicName exhaustive; no special handling flags. + _count: {}, + _addr: {}, +}; + +export function isIntrinsicName(name: string): name is IntrinsicName { + return Object.prototype.hasOwnProperty.call(INTRINSIC_DEFINITIONS, name); +} + +/** + * Route built-in intrinsics to the host implementation (enforcing presence). + * Throws when an intrinsic is missing or returns undefined. + */ +export async function handleIntrinsic( + data: IntrinsicProvider, + container: RefContainer, + name: IntrinsicName, + args: EvalValue[] +): Promise { + // INTRINSIC_DEFINITIONS is a static map of trusted keys. + // eslint-disable-next-line security/detect-object-injection + const intrinsicDef = INTRINSIC_DEFINITIONS[name]; + if (intrinsicDef) { + const { minArgs, maxArgs } = intrinsicDef; + if (minArgs !== undefined && args.length < minArgs) { + throw new Error(`Intrinsic ${name} expects at least ${minArgs} argument(s)`); + } + if (maxArgs !== undefined && args.length > maxArgs) { + throw new Error(`Intrinsic ${name} expects at most ${maxArgs} argument(s)`); + } + } + + // Explicit numeric intrinsics (simple parameter lists) + if (name === '__GetRegVal') { + const fn = data.__GetRegVal; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __GetRegVal'); + } + const out = await fn.call(data, String(args[0] ?? '')); + if (out === undefined) { + throw new Error('Intrinsic __GetRegVal returned undefined'); + } + return out; + } + if (name === '__FindSymbol') { + const fn = data.__FindSymbol; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __FindSymbol'); + } + const out = await fn.call(data, String(args[0] ?? '')); + if (out === undefined) { + throw new Error('Intrinsic __FindSymbol returned undefined'); + } + return out | 0; + } + if (name === '__CalcMemUsed') { + const fn = data.__CalcMemUsed; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __CalcMemUsed'); + } + const n0 = Number(args[0] ?? 0) >>> 0; + const n1 = Number(args[1] ?? 0) >>> 0; + const n2 = Number(args[2] ?? 0) >>> 0; + const n3 = Number(args[3] ?? 0) >>> 0; + const out = await fn.call(data, n0, n1, n2, n3); + if (out === undefined) { + throw new Error('Intrinsic __CalcMemUsed returned undefined'); + } + return out >>> 0; + } + if (name === '__size_of') { + const fn = data.__size_of; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __size_of'); + } + const out = await fn.call(data, String(args[0] ?? '')); + if (out === undefined) { + throw new Error('Intrinsic __size_of returned undefined'); + } + return out | 0; + } + if (name === '__Symbol_exists') { + const fn = data.__Symbol_exists; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __Symbol_exists'); + } + const out = await fn.call(data, String(args[0] ?? '')); + if (out === undefined) { + throw new Error('Intrinsic __Symbol_exists returned undefined'); + } + return out | 0; + } + // Explicit intrinsic that needs the container but returns a number + if (name === '__Offset_of') { + const fn = data.__Offset_of; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __Offset_of'); + } + const out = await fn.call(data, container, String(args[0] ?? '')); + if (out === undefined) { + throw new Error('Intrinsic __Offset_of returned undefined'); + } + return out >>> 0; + } + if (name === '__Running') { + const fn = data.__Running; + if (typeof fn !== 'function') { + throw new Error('Missing intrinsic __Running'); + } + const out = await fn.call(data); + if (out === undefined) { + throw new Error('Intrinsic __Running returned undefined'); + } + return out | 0; + } + + throw new Error(`Missing intrinsic ${name}`); +} + +/** + * Handle pseudo-member access (obj._count / obj._addr) using the host helpers. + */ +export async function handlePseudoMember( + data: IntrinsicProvider, + container: RefContainer, + property: string, + baseRef: ScvdNode +): Promise { + container.member = baseRef; + container.current = baseRef; + container.valueType = undefined; + const fn = property === '_count' ? data._count : data._addr; + if (typeof fn !== 'function') { + throw new Error(`Missing pseudo-member ${property}`); + } + const out = await fn.call(data, container); + if (out === undefined) { + throw new Error(`Pseudo-member ${property} returned undefined`); + } + return out; +} diff --git a/src/views/component-viewer/parser-evaluator/math-ops.ts b/src/views/component-viewer/parser-evaluator/math-ops.ts new file mode 100644 index 00000000..58feec4c --- /dev/null +++ b/src/views/component-viewer/parser-evaluator/math-ops.ts @@ -0,0 +1,256 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Shared numeric helpers used by the evaluator and parser constant folding. + */ + +export type ScalarKind = 'int' | 'uint' | 'float'; + +export interface ScalarType { + kind: ScalarKind; + bits?: number; + name?: string; + typename?: string; +} + +export type MathValue = + | number + | bigint + | string + | boolean + | Uint8Array + // very loose function type to accept evaluator-provided callables + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | ((...args: any[]) => any) + | undefined; + +export function toNumeric(x: MathValue): number | bigint { + if (typeof x === 'number' || typeof x === 'bigint') { + return x; + } + if (typeof x === 'boolean') { + return x ? 1 : 0; + } + if (typeof x === 'string' && x.trim() !== '') { + const n = Number(x); + if (Number.isFinite(n)) { + return n; + } + try { + return BigInt(x); + } catch { + return 0; + } + } + return 0; +} + +export function toBigInt(x: MathValue): bigint { + if (typeof x === 'bigint') { + return x; + } + if (typeof x === 'number') { + return BigInt(Math.trunc(x)); + } + if (typeof x === 'boolean') { + return x ? 1n : 0n; + } + if (typeof x === 'string' && x.trim() !== '') { + try { + return BigInt(x); + } catch { + const n = Number(x); + return BigInt(Math.trunc(Number.isFinite(n) ? n : 0)); + } + } + return 0n; +} + +export function maskToBits(v: number | bigint, bits?: number): number | bigint { + if (!bits || bits <= 0) { + return v; + } + if (typeof v === 'bigint') { + const mask = (1n << BigInt(bits)) - 1n; + return v & mask; + } + if (bits >= 32) { + return v >>> 0; + } + const mask = (1 << bits) - 1; + return (v >>> 0) & mask; +} + +export function normalizeToWidth(v: number | bigint, bits: number | undefined, kind: ScalarKind | 'unknown'): number | bigint { + if (!bits || bits <= 0 || kind === 'float') { + return v; + } + if (kind === 'uint') { + return maskToBits(v, bits); + } + // signed: mask then sign-extend + if (typeof v === 'bigint') { + const mask = (1n << BigInt(bits)) - 1n; + const m = v & mask; + const sign = 1n << BigInt(bits - 1); + return (m & sign) ? m - (1n << BigInt(bits)) : m; + } + const width = bits >= 32 ? 32 : bits; + const mask = width === 32 ? 0xFFFF_FFFF : (1 << width) - 1; + const m = (v >>> 0) & mask; + const sign = 1 << (width - 1); + return (m & sign) ? (m | (~mask)) : m; +} + +export function addVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + if (typeof a === 'string' || typeof b === 'string') { + return String(a) + String(b); + } + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = toBigInt(na) + toBigInt(nb); + return unsigned ? maskToBits(out, bits) : out; + } + const out = (na as number) + (nb as number); + return unsigned ? maskToBits(out, bits) : out; +} + +export function subVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = toBigInt(na) - toBigInt(nb); + return unsigned ? maskToBits(out, bits) : out; + } + const out = (na as number) - (nb as number); + return unsigned ? maskToBits(out, bits) : out; +} + +export function mulVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = toBigInt(na) * toBigInt(nb); + return unsigned ? maskToBits(out, bits) : out; + } + const out = (na as number) * (nb as number); + return unsigned ? maskToBits(out, bits) : out; +} + +export function divVals(a: MathValue, b: MathValue): MathValue { + const nb = toNumeric(b); + if ((typeof nb === 'bigint' && nb === 0n) || (typeof nb === 'number' && nb === 0)) { + throw new Error('Division by zero'); + } + const na = toNumeric(a); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na) / toBigInt(nb); + } + return (na as number) / (nb as number); +} + +export function modVals(a: MathValue, b: MathValue): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + return toBigInt(na) % toBigInt(nb); + } + return ((na as number) | 0) % ((nb as number) | 0); +} + +export function andVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = (toBigInt(na) & toBigInt(nb)); + return unsigned ? maskToBits(out, bits) : out; + } + const out = (((na as number | 0) & (nb as number | 0)) >>> 0); + return unsigned ? maskToBits(out, bits) : out; +} + +export function xorVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = (toBigInt(na) ^ toBigInt(nb)); + return unsigned ? maskToBits(out, bits) : out; + } + const out = (((na as number | 0) ^ (nb as number | 0)) >>> 0); + return unsigned ? maskToBits(out, bits) : out; +} + +export function orVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = (toBigInt(na) | toBigInt(nb)); + return unsigned ? maskToBits(out, bits) : out; + } + const out = (((na as number | 0) | (nb as number | 0)) >>> 0); + return unsigned ? maskToBits(out, bits) : out; +} + +export function shlVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = (toBigInt(na) << BigInt(toBigInt(nb))); + return unsigned ? maskToBits(out, bits) : out; + } + const out = ((na as number | 0) << ((nb as number) & 31)) >>> 0; + return unsigned ? maskToBits(out, bits) : out; +} + +export function sarVals(a: MathValue, b: MathValue, bits?: number, unsigned?: boolean): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const out = (toBigInt(na) >> BigInt(toBigInt(nb))); + return unsigned ? maskToBits(out, bits) : out; + } + const out = ((na as number | 0) >> ((nb as number) & 31)) >>> 0; + return unsigned ? maskToBits(out, bits) : out; +} + +export function shrVals(a: MathValue, b: MathValue, bits?: number): MathValue { + const na = toNumeric(a); + const nb = toNumeric(b); + if (typeof na === 'bigint' || typeof nb === 'bigint') { + const shifted = toBigInt(na) >> BigInt(toBigInt(nb)); + const out = shifted >= 0 ? shifted : 0n; + return maskToBits(out, bits); + } + const out = ((na as number) >>> ((nb as number) & 31)) >>> 0; + return maskToBits(out, bits); +} + +export function mergeKinds(a?: ScalarType, b?: ScalarType): ScalarKind | 'unknown' { + const ka = a?.kind; + const kb = b?.kind; + if (ka === 'float' || kb === 'float') { + return 'float'; + } + if (ka === 'uint' || kb === 'uint') { + return 'uint'; + } + if (ka === 'int' || kb === 'int') { + return 'int'; + } + return 'unknown'; +} diff --git a/src/views/component-viewer/parser-evaluator/model-host.ts b/src/views/component-viewer/parser-evaluator/model-host.ts new file mode 100644 index 00000000..3c45536c --- /dev/null +++ b/src/views/component-viewer/parser-evaluator/model-host.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import type { EvalValue, ScalarKind, ScalarType, RefContainer } from './ref-container'; +import type { DataAccessHost } from '../data-host/access-host'; +import type { ScvdNode } from '../model/scvd-node'; + +export type { EvalValue, ScalarKind, ScalarType, RefContainer, DataAccessHost }; + +// Symbol/model resolution and metadata helpers (no direct memory I/O). +export interface ModelHost { + // Resolution APIs — must set container.current to the resolved ref on success + getSymbolRef(container: RefContainer, name: string, forWrite?: boolean): Promise; + getMemberRef(container: RefContainer, property: string, forWrite?: boolean): Promise; + + // Advanced lookups / intrinsics use the whole container context + resolveColonPath(container: RefContainer, parts: string[]): Promise; // undefined => not found + // Metadata (lets evaluator accumulate offsets itself) + // Bytes per element (including any padding/alignment inside the array layout). + getElementStride(ref: ScvdNode): Promise; // bytes per element + + // Member offset in bytes from base. + getMemberOffset(base: ScvdNode, member: ScvdNode): Promise; // bytes + + // Explicit byte width helper for a ref. + getByteWidth(ref: ScvdNode): Promise; + + // Provide an element model (prototype/type) for array-ish refs. + getElementRef(ref: ScvdNode): Promise; + + /** + * Optional: return the scalar type of the value designated by `container`. + * + * You can return either: + * - a C-like typename string, e.g. "uint32_t", "int16_t", "float" + * - a normalized ScalarType + */ + getValueType(container: RefContainer): Promise; + + /** + * Optional printf formatting hook used by % specifiers in PrintfExpression. + * If it returns a string, the evaluator uses it. If it returns undefined, + * the evaluator falls back to its built-in formatting. + */ + formatPrintf?( + spec: string, + value: EvalValue, + container: RefContainer + ): Promise; +} diff --git a/src/views/component-viewer/parser-evaluator/parser.ts b/src/views/component-viewer/parser-evaluator/parser.ts new file mode 100644 index 00000000..dee6e74f --- /dev/null +++ b/src/views/component-viewer/parser-evaluator/parser.ts @@ -0,0 +1,1085 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +import { addVals, andVals, divVals, maskToBits, modVals, mulVals, orVals, sarVals, shlVals, subVals, toNumeric, xorVals } from './math-ops'; +import { INTRINSIC_DEFINITIONS, isIntrinsicName } from './intrinsics'; +import type { IntrinsicName as HostIntrinsicName } from './intrinsics'; + +export type ValueType = 'number' | 'boolean' | 'string' | 'unknown'; + +export type ConstValue = number | string | boolean | undefined; + +export interface BaseNode { kind: string; start: number; end: number; valueType?: ValueType; constValue?: ConstValue; } +export interface NumberLiteral extends BaseNode { kind:'NumberLiteral'; value:number; raw:string; valueType:'number'; } +export interface StringLiteral extends BaseNode { kind:'StringLiteral'; value:string; raw:string; valueType:'string'; } +export interface BooleanLiteral extends BaseNode { kind:'BooleanLiteral'; value:boolean; valueType:'boolean'; } +export interface Identifier extends BaseNode { kind:'Identifier'; name:string; } +export interface MemberAccess extends BaseNode { kind:'MemberAccess'; object: ASTNode; property: string; } +export interface ArrayIndex extends BaseNode { kind:'ArrayIndex'; array: ASTNode; index: ASTNode; } +export interface UnaryExpression extends BaseNode { kind:'UnaryExpression'; operator:'+'|'-'|'!'|'~'; argument:ASTNode; } +export interface BinaryExpression extends BaseNode { kind:'BinaryExpression'; operator:string; left:ASTNode; right:ASTNode; } +export interface ConditionalExpression extends BaseNode { kind:'ConditionalExpression'; test:ASTNode; consequent:ASTNode; alternate:ASTNode; } + +export interface AssignmentExpression extends BaseNode { + kind:'AssignmentExpression'; + operator:'='|'+='|'-='|'*='|'/='|'%='|'<<='|'>>='|'&='|'^='|'|='; + left:ASTNode; right:ASTNode; +} + +export interface UpdateExpression extends BaseNode { + kind:'UpdateExpression'; + operator:'++'|'--'; + argument: ASTNode; + // true for prefix (++x), false for postfix (x++) + prefix: boolean; +} + +// Colon selector for `typedef_name:member` and `typedef_name:member:enum` +export interface ColonPath extends BaseNode { + kind:'ColonPath'; + parts: string[]; // e.g., ["MyType","field"] or ["MyType","field","EnumVal"] +} + +export type IntrinsicName = HostIntrinsicName; + +export interface CallExpression extends BaseNode { kind:'CallExpression'; callee:ASTNode; args:ASTNode[]; intrinsic?: undefined; } +export interface EvalPointCall extends BaseNode { kind:'EvalPointCall'; callee:ASTNode; args:ASTNode[]; intrinsic:IntrinsicName; } +export type FormatSpec = string; // accept ANY spec char after '%' +export interface TextSegment extends BaseNode { kind:'TextSegment'; text:string; } +export interface FormatSegment extends BaseNode { kind:'FormatSegment'; spec:FormatSpec; value:ASTNode; } +export interface PrintfExpression extends BaseNode { kind:'PrintfExpression'; segments:(TextSegment|FormatSegment)[]; resultType:'string'; } +export interface ErrorNode extends BaseNode { kind:'ErrorNode'; message:string; } + +export type ASTNode = + | NumberLiteral | StringLiteral | BooleanLiteral | Identifier | MemberAccess | ArrayIndex + | UnaryExpression | BinaryExpression | ConditionalExpression | AssignmentExpression | UpdateExpression + | ColonPath + | CallExpression | EvalPointCall | PrintfExpression | TextSegment | FormatSegment | ErrorNode; + +export interface Diagnostic { type: 'error'|'warning'|'info'; message: string; start: number; end: number; } +export interface ParseResult { + ast: ASTNode; + diagnostics: Diagnostic[]; + externalSymbols: string[]; + isPrintf: boolean; + constValue?: ConstValue; // exists when the entire expression folds to a constant +} + +/* ---------------- Tokenizer ---------------- */ + +type TokenKind = 'EOF'|'IDENT'|'NUMBER'|'STRING'|'PUNCT'|'UNKNOWN'; +interface Token { kind: TokenKind; value: string; start: number; end: number; } + +// include ++, --, and C compound assignment ops; keep longer tokens before shorter ones +const MULTI = [ + '>>=','<<=', + '++','--', + '&&','||','==','!=','<=','>=','<<','>>', + '+=','-=','*=','/=','%=','&=','^=','|=' +] as const; + +const SINGLE = new Set('()[]{}.,:?;+-*/%&|^!~<>= '.split('')); + +class Tokenizer { + private s: string = ''; + private i = 0; + private n = 0; + constructor(s: string) { + this.reset(s); + } + public reset(s: string) { + this.s = s; + this.i = 0; + this.n = s.length; + } + public eof() { + return this.i >= this.n; + } + public peek(k=0) { + const j = this.i + k; + return j < this.n ? this.s.charAt(j) : ''; + } + public advance(k=1) { + this.i += k; + } + public skipWS() { + while (!this.eof() && /\s/.test(this.s.charAt(this.i))) { + this.i++; + } + } + public next(): Token { + this.skipWS(); + if (this.eof()) { + return { kind:'EOF', value:'', start:this.i, end:this.i }; + } + + for (const m of MULTI) { + if (this.s.startsWith(m, this.i)) { + const start = this.i; this.advance(m.length); + return { kind:'PUNCT', value:m as string, start, end:this.i }; + } + } + + const ch = this.peek(0); + + const isDigit = (c:string)=> c >= '0' && c <= '9'; + if (isDigit(ch) || (ch === '.' && isDigit(this.peek(1)))) { + const start = this.i; + if (ch === '0' && (this.peek(1).toLowerCase() === 'x')) { + this.advance(2); while (!this.eof() && /[0-9a-f]/i.test(this.peek())) { + this.advance(); + } + } else if (ch === '0' && (this.peek(1).toLowerCase() === 'b')) { + this.advance(2); while (!this.eof() && /[01]/.test(this.peek())) { + this.advance(); + } + } else if (ch === '0' && (this.peek(1).toLowerCase() === 'o')) { + this.advance(2); while (!this.eof() && /[0-7]/.test(this.peek())) { + this.advance(); + } + } else { + while (!this.eof() && /[0-9_]/.test(this.peek())) { + this.advance(); + } + if (this.peek() === '.') { + this.advance(); while (!this.eof() && /[0-9_]/.test(this.peek())) { + this.advance(); + } + } + if (this.peek().toLowerCase() === 'e') { + this.advance(); if (/[+-]/.test(this.peek())) { + this.advance(); + } while (!this.eof() && /[0-9]/.test(this.peek())) { + this.advance(); + } + } + } + const raw = this.s.slice(start, this.i); + return { kind:'NUMBER', value:raw, start, end:this.i }; + } + + const isAlpha = (c:string)=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_'; + if (isAlpha(ch)) { + const start = this.i; this.advance(); + while (!this.eof() && (isAlpha(this.peek()) || /[0-9]/.test(this.peek()))) { + this.advance(); + } + const val = this.s.slice(start, this.i); + return { kind:'IDENT', value:val, start, end:this.i }; + } + + if (ch === '"' || ch === '\'') { + const quote = ch; + const start = this.i; + this.advance(); + let escaped = false; + while (!this.eof()) { + const c = this.peek(); + this.advance(); + if (escaped) { + escaped = false; + } else if (c === '\\') { + escaped = true; + } else if (c === quote) { + break; + } + } + return { kind:'STRING', value:this.s.slice(start, this.i), start, end:this.i }; + } + + if (SINGLE.has(ch)) { + const start = this.i; + this.advance(); + return { kind:'PUNCT', value:ch, start, end:this.i }; + } + + const start = this.i; + const u = this.peek(); + this.advance(); + return { kind:'UNKNOWN', value:u, start, end:this.i }; + } +} + +/* ---------------- Parser ---------------- */ + +function span(start:number, end:number) { + return { start, end }; +} +const startOf = (n: ASTNode) => (n as BaseNode).start; +const endOf = (n: ASTNode) => (n as BaseNode).end; +const constOf = (n: ASTNode) => (n as BaseNode).constValue; +const makeNumberLiteral = (value: number, start: number, end: number): NumberLiteral => ({ + kind: 'NumberLiteral', + value, + raw: value.toString(), + valueType: 'number', + constValue: value, + ...span(start, end), +}); +function literalFromConst(cv: ConstValue, start: number, end: number): ASTNode { + if (typeof cv === 'number') { + return makeNumberLiteral(cv, start, end); + } + if (typeof cv === 'string') { + return { kind: 'StringLiteral', value: cv, raw: JSON.stringify(cv), valueType: 'string', constValue: cv, ...span(start, end) }; + } + if (typeof cv === 'boolean') { + return { kind: 'BooleanLiteral', value: cv, valueType: 'boolean', constValue: cv, ...span(start, end) }; + } + return { kind: 'ErrorNode', message: 'Unsupported literal', ...span(start, end) }; +} + +function numFromRaw(raw: string): number { + try { + const cleaned = raw.replace(/_/g, ''); + if (/^0[xX]/.test(cleaned)) { + return parseInt(cleaned.slice(2), 16); + } + if (/^0[bB]/.test(cleaned)) { + return parseInt(cleaned.slice(2), 2); + } + if (/^0[oO]/.test(cleaned)) { + return parseInt(cleaned.slice(2), 8); + } + if (cleaned.includes('.') || /e/i.test(cleaned)) { + return parseFloat(cleaned); + } // decimal float + return parseInt(cleaned, 10); + } catch { + /* istanbul ignore next -- defensive fallback; hard to trigger parsing failure */ + return NaN; + } +} + +function unescapeString(rawWithQuotes: string): string { + const s = rawWithQuotes.slice(1, -1); + let out = ''; + for (let i = 0; i < s.length; i++) { + const ch = s.charAt(i); + if (ch !== '\\') { + out += ch; continue; + } + i++; + if (i >= s.length) { + out += '\\'; break; + } + const e = s.charAt(i); + switch (e) { + case 'n': out += '\n'; break; + case 'r': out += '\r'; break; + case 't': out += '\t'; break; + case 'b': out += '\b'; break; + case 'f': out += '\f'; break; + case 'v': out += '\v'; break; + case '\\': out += '\\'; break; + case '"': out += '"'; break; + case '\'': out += '\''; break; + case '0': out += '\0'; break; + case 'x': { + const h1 = s.charAt(i+1), h2 = s.charAt(i+2); + if (h1 && h2 && /[0-9a-fA-F]/.test(h1) && /[0-9a-fA-F]/.test(h2)) { + out += String.fromCharCode(parseInt(h1 + h2, 16)); + i += 2; + } else { + out += 'x'; + } + break; + } + case 'u': { + if (s.charAt(i+1) === '{') { + let j = i + 2, hex = ''; + while (j < s.length && s.charAt(j) !== '}') { + hex += s.charAt(j); j++; + } + if (s.charAt(j) === '}' && /^[0-9a-fA-F]+$/.test(hex)) { + out += String.fromCodePoint(parseInt(hex, 16)); i = j; + } else { + out += 'u'; + } + } else { + const h = s.substr(i+1, 4); + if (/^[0-9a-fA-F]{4}$/.test(h)) { + out += String.fromCharCode(parseInt(h, 16)); i += 4; + } else { + out += 'u'; + } + } + break; + } + default: out += e; break; + } + } + return out; +} + +function normalizeConstValue(v: unknown): ConstValue { + /* istanbul ignore next -- parser never produces BigInt; only reachable in synthetic tests */ + if (typeof v === 'bigint') { + return Number(v); + } + /* istanbul ignore else -- defensive guard, practical inputs are always numeric/string/boolean */ + if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean') { + return v; + } + /* istanbul ignore next -- defensive fallback for unexpected const types */ + return undefined; +} + +function foldBinaryConst(op: string, a: ConstValue, b: ConstValue): ConstValue | undefined { + // Probe numeric coercions early to surface any user-defined valueOf/toString errors (legacy behavior). + Number(a as number); + Number(b as number); + + switch (op) { + case '+': return normalizeConstValue(addVals(a, b)); + case '-': return normalizeConstValue(subVals(a, b)); + case '*': return normalizeConstValue(mulVals(a, b)); + case '/': { + const bn = toNumeric(b); + /* istanbul ignore else -- zero is caught above; other cases return numeric */ + if ((typeof bn === 'number' && bn === 0) || (typeof bn === 'bigint' && bn === 0n)) { + return undefined; + } + return normalizeConstValue(divVals(a, b)); + } + case '%': { + const bn = toNumeric(b); + /* istanbul ignore else -- zero is caught above; other cases return numeric */ + if ((typeof bn === 'number' && bn === 0) || (typeof bn === 'bigint' && bn === 0n)) { + return undefined; + } + return normalizeConstValue(modVals(a, b)); + } + case '<<': return normalizeConstValue(shlVals(a, b)); + case '>>': return normalizeConstValue(sarVals(a, b)); + case '&': return normalizeConstValue(andVals(a, b)); + case '^': return normalizeConstValue(xorVals(a, b)); + case '|': return normalizeConstValue(orVals(a, b)); + } + return undefined; +} + +function isZeroConst(v: ConstValue): boolean { + const n = toNumeric(v); + return (typeof n === 'number') ? n === 0 : n === 0n; +} + +export class Parser { + private s = ''; + private tok = new Tokenizer(''); + private cur: Token = this.tok.next(); + private diagnostics: Diagnostic[] = []; + private externals: Set = new Set(); + + /* ---------- lifecycle ---------- */ + + private reinit(s:string) { + this.s = s; + this.tok.reset(s); + this.cur = this.tok.next(); + this.diagnostics = []; + this.externals.clear(); + } + // Public alias for consumers that want to reuse the instance + public reset(s:string) { + this.reinit(s); + } + + /* ---------- public API ---------- */ + + /** + * Wrapper that keeps diagnostics when the underlying parse throws. + */ + public parseWithDiagnostics(input: string, isPrintExpression: boolean): ParseResult { + try { + return this.parse(input, isPrintExpression); + } catch (e) { + const start = 0; + const end = Math.max(input.length, 0); + const errors = (e instanceof AggregateError && Array.isArray(e.errors)) ? e.errors : [e]; + for (const err of errors) { + const message = err instanceof Error ? err.message : String(err); + this.error(message, start, end); + } + const message = errors.length ? (errors[0] instanceof Error ? errors[0].message : String(errors[0])) : 'Unknown parser error'; + const ast: ErrorNode = { kind: 'ErrorNode', message, start, end }; + return { + ast, + diagnostics: this.diagnostics.slice(), + externalSymbols: [], + isPrintf: isPrintExpression, + }; + } + } + + public parse(input: string, isPrintExpression: boolean): ParseResult { + this.reinit(input); + + let ast: ASTNode; + let isPrintf = false; + + if (isPrintExpression) { + // Always treat as printf, even if it's pure text like "foobar" + ast = this.parsePrintfExpression(); + isPrintf = true; + + // Force EOF so tokenizer-based trailing checks don't run in printf mode + this.cur = { kind: 'EOF', value: '', start: input.length, end: input.length }; + + } else if (this.looksLikePrintf(input)) { + // Auto-detect printf only when not explicitly forced + ast = this.parsePrintfExpression(); + isPrintf = true; + this.cur = { kind: 'EOF', value: '', start: input.length, end: input.length }; + + } else { + // Normal expression parsing + ast = this.parseExpression(); + while (this.cur.kind === 'PUNCT' && this.cur.value === ';') { + this.eat('PUNCT',';'); + } + if (this.cur.kind !== 'EOF') { + this.warn('Extra tokens after expression', this.cur.start, this.cur.end); + } + } + + // Constant folding only for non-printf ASTs + ast = this.fold(ast); + const constValue = isPrintf ? undefined : ast.constValue; + + return { + ast, + diagnostics: this.diagnostics.slice(), + externalSymbols: Array.from(this.externals).sort(), + isPrintf, + constValue, + }; + } + + /* ---------- diagnostics & token helpers ---------- */ + + private error(msg:string, start:number, end:number) { + this.diagnostics.push({ type:'error', message:msg, start, end }); + } + private warn(msg:string, start:number, end:number) { + this.diagnostics.push({ type:'warning', message:msg, start, end }); + } + + private eat(kind:TokenKind, value?:string): Token { + const t = this.cur; + if (t.kind !== kind || (value !== undefined && t.value !== value)) { + this.error(`Expected ${kind} ${value ?? ''} but found ${t.kind} ${JSON.stringify(t.value)}`, t.start, t.end); + return t; + } + this.cur = this.tok.next(); + return t; + } + private tryEat(kind:TokenKind, value?:string): Token|undefined { + const t = this.cur; + if (t.kind === kind && (value === undefined || t.value === value)) { + this.cur = this.tok.next(); return t; + } + return undefined; + } + private curIs(kind: TokenKind, value?: string): boolean { + const t = this.cur; + return t.kind === kind && (value === undefined || t.value === value); + } + + // Generic printf detection: %% or %x[ ... ] for ANY non-space spec x + private looksLikePrintf(s:string): boolean { + if (s.includes('%%')) { + return true; + } + return /%[^\s%]\s*\[/.test(s); + } + + private isAssignable(n: ASTNode): boolean { + return n.kind === 'Identifier' || n.kind === 'MemberAccess' || n.kind === 'ArrayIndex'; + } + + /* ---------- printf parsing ---------- */ + + // Parse a printf-style template from the raw input string into segments. + private parsePrintfExpression(): PrintfExpression { + const s = this.s; + const n = s.length; + let i = 0; + const segments: (TextSegment|FormatSegment)[] = []; + + while (i < n) { + const j = s.indexOf('%', i); + if (j === -1) { + /* istanbul ignore else -- loop guard ensures i <= n */ + if (i < n) { + segments.push({ kind:'TextSegment', text:s.slice(i), ...span(i,n) }); + } + break; + } + if (j > i) { + segments.push({ kind:'TextSegment', text:s.slice(i,j), ...span(i,j) }); + } + + // Handle escaped percent + if (j+1 < n && s.charAt(j+1) === '%') { + segments.push({ kind:'TextSegment', text:'%', ...span(j,j+2) }); + i = j+2; continue; + } + + // Accept ANY single spec character after '%' + const spec = (j+1 < n) ? s.charAt(j+1) : ''; + if (spec && spec !== '%') { + // Look for a bracketed expression after optional whitespace + let k = j + 2; + while (k=n || s.charAt(k) !== '[') { + // Not a bracket form: treat literally as "%x" + segments.push({ kind:'TextSegment', text:'%'+spec, ...span(j,j+2) }); + i = j+2; continue; + } + + // Balanced scan for %[ ... ] with string awareness + const exprStart = k+1; + let depth = 1; + let m = exprStart; + let inString: '"'|'\''|null = null; + let escaped = false; + while (m < n && depth > 0) { + const c = s.charAt(m); + if (inString) { + if (escaped) { + escaped = false; m++; continue; + } + if (c === '\\') { + escaped = true; m++; continue; + } + if (c === inString) { + inString = null; m++; continue; + } + m++; continue; + } + if (c === '"' || c === '\'') { + inString = c as '"'|'\''; m++; continue; + } + if (c === '[') { + depth++; m++; continue; + } + if (c === ']') { + depth--; if (depth === 0) { + break; + } m++; continue; + } + m++; + } + + let exprEnd = m; + if (depth !== 0) { + this.warn('Unclosed formatter bracket; treating rest as expression.', j, n); exprEnd = n; + } + + const inner = this.parseSubexpression(s.slice(exprStart, exprEnd), exprStart); + const seg: FormatSegment = { kind:'FormatSegment', spec: spec as FormatSpec, value: inner, ...span(j, depth===0? exprEnd+1 : n) }; + segments.push(seg); + i = (depth===0? exprEnd+1 : n); + continue; + } + + // Lone '%' + segments.push({ kind:'TextSegment', text:'%', ...span(j,j+1) }); + i = j+1; + } + return { kind:'PrintfExpression', segments, resultType:'string', ...span(0,n) }; + } + + // Parse a subexpression with a fresh tokenizer, adjust diagnostics offsets. + private parseSubexpression(exprSrc: string, baseOffset: number): ASTNode { + const savedS = this.s, savedTok = this.tok, savedCur = this.cur, savedDiag = this.diagnostics, savedExt = this.externals; + const t = new Tokenizer(exprSrc); + this.s = exprSrc; + this.tok = t; + this.cur = t.next(); + const tmp: Diagnostic[] = []; + this.diagnostics = tmp; + this.externals = new Set(); + + const node = this.parseExpression(); + const folded = this.fold(node); + + // consume optional semicolons and check for trailing junk + while (this.cur.kind === 'PUNCT' && this.cur.value === ';') { + this.eat('PUNCT',';'); + } + if (this.cur.kind !== 'EOF') { + tmp.push({ type:'warning', message:'Extra tokens after expression', start:this.cur.start + baseOffset, end:this.cur.end + baseOffset }); + } + + const adj = tmp.map(d => ({ ...d, start: d.start + baseOffset, end: d.end + baseOffset })); + savedDiag.push(...adj); + for (const sym of this.externals) { + savedExt.add(sym); + } + this.s = savedS; + this.tok = savedTok; + this.cur = savedCur; + this.diagnostics = savedDiag; + this.externals = savedExt; + return folded; + } + + /* ---------- expression parsing ---------- */ + + private parseExpression(): ASTNode { + return this.parseAssignment(); + } + + private parseConditional(): ASTNode { + let node = this.parseBinary(1); + if (this.cur.kind === 'PUNCT' && this.cur.value === '?') { + this.eat('PUNCT','?'); + const cons = this.parseExpression(); + if (!this.tryEat('PUNCT',':')) { + this.error('Expected ":" in conditional expression', this.cur.start, this.cur.end); + } + const alt = this.parseExpression(); + node = { kind:'ConditionalExpression', test:node, consequent:cons, alternate:alt, ...span(startOf(node), endOf(alt)) }; + } + return node; + } + + private static PREC: Map = new Map([ + ['||', 1], + ['&&', 2], + ['|', 3], + ['^', 4], + ['&', 5], + ['==', 6], ['!=', 6], + ['<', 7], ['>', 7], ['<=', 7], ['>=', 7], + ['>>', 8], ['<<', 8], + ['+', 9], ['-', 9], + ['*', 10], ['/', 10], ['%', 10], + ]); + + private parseAssignment(): ASTNode { + const left = this.parseConditional(); + if (this.cur.kind === 'PUNCT') { + const op = this.cur.value; + const assignOps = new Set(['=','+=','-=','*=','/=','%=','<<=','>>=','&=','^=','|=']); + + if (assignOps.has(op)) { + this.eat('PUNCT', op); + if (!this.isAssignable(left)) { + this.error('Invalid assignment target', startOf(left), endOf(left)); + } + if (left.kind === 'Identifier') { + this.externals.delete((left as Identifier).name); + } + const right = this.parseAssignment(); // right-assoc + return { kind:'AssignmentExpression', operator: op as AssignmentExpression['operator'], left, right, ...span(startOf(left), endOf(right)) }; + } + } + return left; + } + + private parseBinary(minPrec: number): ASTNode { + let node = this.parseUnary(); + while (this.cur.kind === 'PUNCT' && Parser.PREC.has(this.cur.value)) { + const op = this.cur.value; + const prec = Parser.PREC.get(op) ?? 0; + if (prec < minPrec) { + break; + } + this.eat('PUNCT', op); + const rhs = this.parseBinary(prec + 1); + node = { kind:'BinaryExpression', operator:op, left:node, right:rhs, ...span(startOf(node), endOf(rhs)) }; + } + return node; + } + + private parseUnary(): ASTNode { + const punct = this.cur.kind === 'PUNCT' ? this.cur.value : undefined; + + if (punct && (punct === '++' || punct === '--')) { + const op = punct; + const t = this.eat('PUNCT', op); + const arg = this.parseUnary(); + if (!this.isAssignable(arg)) { + this.error('Invalid increment/decrement target', startOf(arg), endOf(arg)); + } + return { kind:'UpdateExpression', operator: op as UpdateExpression['operator'], argument: arg, prefix: true, ...span(t.start, endOf(arg)) }; + } + + if (punct && ['+', '-', '!', '~'].includes(punct)) { + const op = punct; + const t = this.eat('PUNCT', op); + const arg = this.parseUnary(); + return { kind:'UnaryExpression', operator:op as UnaryExpression['operator'], argument:arg, ...span(t.start, endOf(arg)) }; + } + + return this.parsePostfix(); + } + + private parsePostfix(): ASTNode { + let node = this.parsePrimary(); + while (true) { + // colon-type/member/enum selector chain: typedef_name:member[:enum] + if ((node.kind === 'Identifier' || node.kind === 'ColonPath') && this.curIs('PUNCT', ':')) { + this.eat('PUNCT', ':'); + let parts: string[]; + const startPos = startOf(node); + let lastEnd = endOf(node); + if (node.kind === 'Identifier') { + this.externals.delete((node as Identifier).name); + parts = [(node as Identifier).name]; + } else { + parts = [...(node as ColonPath).parts]; + } + if (!this.curIs('IDENT')) { + this.error('Expected identifier after ":"', this.cur.start, this.cur.end); + } else { + const first = this.eat('IDENT'); + parts.push(first.value); + lastEnd = first.end; + while (this.curIs('PUNCT', ':')) { + this.eat('PUNCT', ':'); + if (!this.curIs('IDENT')) { + this.error('Expected identifier after ":"', this.cur.start, this.cur.end); + break; + } + const idt = this.eat('IDENT'); + parts.push(idt.value); + lastEnd = idt.end; + } + } + node = { kind:'ColonPath', parts, valueType:'unknown', ...span(startPos, lastEnd) }; + continue; + } + + // If next token is ':' but we're not on Identifier/ColonPath, it's likely the ternary ':'; stop here. + if (this.curIs('PUNCT', ':')) { + break; + } + + // function call + if (this.tryEat('PUNCT','(')) { + const args: ASTNode[] = []; + if (!(this.cur.kind === 'PUNCT' && this.cur.value === ')')) { + while (true) { + args.push(this.parseExpression()); + if (this.tryEat('PUNCT',',')) { + continue; + } + break; + } + } + if (!this.tryEat('PUNCT',')')) { + this.error('Expected ")"', this.cur.start, this.cur.end); + } + const callee = node as ASTNode; + const isIntrinsic = callee.kind === 'Identifier' && isIntrinsicName((callee as Identifier).name); + const calleeName = callee.kind === 'Identifier' ? (callee as Identifier).name : undefined; + if (isIntrinsic && calleeName) { + const intrinsicDef = INTRINSIC_DEFINITIONS[calleeName as IntrinsicName]; + if (intrinsicDef) { + const { minArgs, maxArgs } = intrinsicDef; + if (minArgs !== undefined && args.length < minArgs) { + this.error(`Intrinsic ${calleeName} expects at least ${minArgs} argument(s)`, startOf(node), this.cur.end); + } + if (maxArgs !== undefined && args.length > maxArgs) { + this.error(`Intrinsic ${calleeName} expects at most ${maxArgs} argument(s)`, startOf(node), this.cur.end); + } + } + const callNode: EvalPointCall = { + kind: 'EvalPointCall', + callee, + args, + intrinsic: calleeName as IntrinsicName, + valueType: 'number' as const, + ...span(startOf(node), this.cur.end) + }; + node = callNode; + } else { + const callNode: CallExpression = { + kind: 'CallExpression', + callee, + args, + ...span(startOf(node), this.cur.end) + }; + node = callNode; + } + continue; + } + // property access + if (this.tryEat('PUNCT','.')) { + if (this.cur.kind === 'IDENT') { + const prop = this.cur.value; + const idt = this.eat('IDENT'); + node = { kind:'MemberAccess', object:node, property:prop, ...span(startOf(node), idt.end) }; + } else { + this.error('Expected identifier after "."', this.cur.start, this.cur.end); + } + continue; + } + // index access + if (this.tryEat('PUNCT', '[')) { + const index = this.parseExpression(); + if (!this.tryEat('PUNCT', ']')) { + this.error('Expected "]"', this.cur.start, this.cur.end); + } + node = { kind:'ArrayIndex', array:node, index, ...span(startOf(node), endOf(index)) }; + continue; + } // postfix ++ / -- + if (this.cur.kind === 'PUNCT' && (this.cur.value === '++' || this.cur.value === '--')) { + const op = this.cur.value; + const t = this.eat('PUNCT', op); + if (!this.isAssignable(node)) { + this.error('Invalid increment/decrement target', startOf(node), endOf(node)); + } + node = { kind:'UpdateExpression', operator: op as UpdateExpression['operator'], argument: node, prefix: false, ...span(startOf(node), t.end) }; + break; + } + break; + } + return node; + } + + private parsePrimary(): ASTNode { + const t = this.cur; + if (t.kind === 'NUMBER') { + this.eat('NUMBER'); + const val = numFromRaw(t.value); + return { kind:'NumberLiteral', value:val, raw:t.value, valueType:'number', constValue: val, ...span(t.start,t.end) }; + } + if (t.kind === 'STRING') { + this.eat('STRING'); + const text = unescapeString(t.value); + const isCharLiteral = t.value.startsWith('\'') && t.value.endsWith('\''); + if (isCharLiteral) { + const code = text.codePointAt(0) ?? 0; + const val = (code >>> 0); + return { kind:'NumberLiteral', value:val, raw:t.value, valueType:'number', constValue: val, ...span(t.start,t.end) }; + } + return { kind:'StringLiteral', value:text, raw:t.value, valueType:'string', constValue: text, ...span(t.start,t.end) }; + } + if (t.kind === 'IDENT' && (t.value === 'true' || t.value === 'false')) { + this.eat('IDENT'); + const val = t.value === 'true'; + return { kind:'BooleanLiteral', value: val, valueType:'boolean', constValue: val, ...span(t.start,t.end) }; + } + if (t.kind === 'IDENT') { + this.eat('IDENT'); + const node: Identifier = { kind:'Identifier', name:t.value, valueType:'unknown', ...span(t.start,t.end) }; + if (!isIntrinsicName(t.value)) { + this.externals.add(t.value); + } + return node; + } + if (t.kind === 'PUNCT' && t.value === '(') { + this.eat('PUNCT','('); + const expr = this.parseExpression(); + if (!this.tryEat('PUNCT',')')) { + this.error('Expected ")"', this.cur.start, this.cur.end); + } + return expr; + } + this.error(`Unexpected token ${t.kind} ${JSON.stringify(t.value)}`, t.start, t.end); + this.eat(t.kind); + return { kind:'ErrorNode', message:'Unexpected token', ...span(t.start,t.end) }; + } + + /* ---------- constant folding ---------- */ + + private fold(node: ASTNode): ASTNode { + const k = node.kind; + + if (k === 'NumberLiteral' || k === 'StringLiteral' || k === 'BooleanLiteral') { + return { ...node, constValue: node.value }; + } + if (k === 'Identifier') { + return node; + } + if (k === 'ColonPath') { + return node; + } + if (k === 'MemberAccess') { + return { ...node, object: this.fold((node as MemberAccess).object) }; + } + if (k === 'ArrayIndex') { + return { ...node, array: this.fold((node as ArrayIndex).array), index: this.fold((node as ArrayIndex).index) }; + } + + if (k === 'UnaryExpression') { + const arg = this.fold((node as UnaryExpression).argument); + const op = (node as UnaryExpression).operator; + const res: UnaryExpression & { constValue?: ConstValue } = { ...(node as UnaryExpression), argument: arg }; + const v = constOf(arg); + if (v !== undefined) { + try { + let cv: ConstValue; + if (op === '+') { + cv = Number(v); + } else if (op === '-') { + cv = -Number(v); + } else if (op === '!') { + cv = !v; + } else if (op === '~') { + const toggled = maskToBits(~Number(v), 32); + /* istanbul ignore next -- BigInt path only triggered by synthetic inputs */ + cv = typeof toggled === 'bigint' ? Number(toggled) : toggled; + } + if (cv !== undefined) { + res.constValue = cv; + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + this.error(`Failed to fold unary expression ${op}: ${msg}`, startOf(node), endOf(node)); + } + if (res.constValue !== undefined) { + return literalFromConst(res.constValue, startOf(node), endOf(node)); + } + } + return res; + } + + if (k === 'UpdateExpression') { + const ue = node as UpdateExpression; + return { ...ue, argument: this.fold(ue.argument) }; + } + + if (k === 'BinaryExpression') { + const left = this.fold((node as BinaryExpression).left); + const right = this.fold((node as BinaryExpression).right); + const op = (node as BinaryExpression).operator; + const res: BinaryExpression & { constValue?: ConstValue } = { ...(node as BinaryExpression), left, right }; + const la = constOf(left); const ra = constOf(right); + const hasL = la !== undefined; const hasR = ra !== undefined; + if (hasL && hasR) { + try { + let cv: ConstValue; + const a = la as Exclude; + const b = ra as Exclude; + switch (op) { + case '==': cv = a == b; break; + case '!=': cv = a != b; break; + case '<': cv = a < b; break; + case '<=': cv = a <= b; break; + case '>': cv = a > b; break; + case '>=': cv = a >= b; break; + case '&&': cv = !!a && !!b; break; + case '||': cv = !!a || !!b; break; + default: { + const folded = foldBinaryConst(op, a, b); + if (folded === undefined) { + if ((op === '/' || op === '%') && isZeroConst(b)) { + this.error('Division by zero', startOf(node), endOf(node)); + } + } else { + cv = folded; + } + break; + } + } + if (cv !== undefined) { + res.constValue = cv; + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + this.error(`Failed to fold binary expression ${op}: ${msg}`, startOf(node), endOf(node)); + } + if (res.constValue !== undefined) { + return literalFromConst(res.constValue, startOf(node), endOf(node)); + } + } else { + if (op === '&&' && hasL && !la) { + res.constValue = false; + } + if (op === '||' && hasL && la) { + res.constValue = true; + } + } + + // Partial folding: combine trailing numeric constants in addition chains (e.g., foo+1+2 => foo+3) + if (op === '+' && right.constValue !== undefined && typeof right.constValue === 'number') { + const rightVal = Number(right.constValue); + // Pattern: (X + constA) + constB + if (left.kind === 'BinaryExpression') { + const lb = left as BinaryExpression; + if (lb.operator === '+' && lb.right.constValue !== undefined && typeof lb.right.constValue === 'number') { + const combined = Number(lb.right.constValue) + rightVal; + const newRight = makeNumberLiteral(combined, startOf(right), endOf(right)); + const newLeft = lb.left; + return { + kind: 'BinaryExpression', + operator: '+', + left: newLeft, + right: newRight, + ...span(startOf(newLeft), endOf(newRight)), + }; + } + } + } + return res; + } + + if (k === 'AssignmentExpression') { + const ae = node as AssignmentExpression; + const right = this.fold(ae.right); + // Do not fold assignments to constants; keep side effects for evaluator + return { ...ae, right }; + } + + if (k === 'ConditionalExpression') { + const test = this.fold((node as ConditionalExpression).test); + const cons = this.fold((node as ConditionalExpression).consequent); + const alt = this.fold((node as ConditionalExpression).alternate); + const res: ConditionalExpression & { constValue?: ConstValue } = { ...(node as ConditionalExpression), test, consequent: cons, alternate: alt }; + const testConst = constOf(test); + if (testConst !== undefined) { + res.constValue = (testConst ? constOf(cons) : constOf(alt)); + } + return res; + } + + if (k === 'CallExpression' || k === 'EvalPointCall') { + const args = (node as CallExpression | EvalPointCall).args.map((a:ASTNode)=> this.fold(a)); + return { ...(node as CallExpression | EvalPointCall), args }; + } + + if (k === 'PrintfExpression') { + return { ...node, segments: (node as PrintfExpression).segments.map(seg => { + if (seg.kind === 'FormatSegment') { + return { ...seg, value: this.fold(seg.value) }; + } + return seg; + }) }; + } + + return node; + } +} + +/* -------- Convenience singleton and API -------- */ + +// Internal helpers exposed for tests. +export const __parserTestUtils = { literalFromConst }; + +export const defaultParser = new Parser(); +export function parseExpression(expr: string, isPrintExpression: boolean): ParseResult { + return defaultParser.parseWithDiagnostics(expr, isPrintExpression); +} diff --git a/src/views/component-viewer/parser-evaluator/ref-container.ts b/src/views/component-viewer/parser-evaluator/ref-container.ts new file mode 100644 index 00000000..3ff829d3 --- /dev/null +++ b/src/views/component-viewer/parser-evaluator/ref-container.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// generated with AI + +import type { ScalarKind, ScalarType } from './math-ops'; +import type { ScvdNode } from '../model/scvd-node'; + +export type EvalValue = + | number + | bigint + | string + | boolean + | Uint8Array + | ((...args: EvalValue[]) => Promise) + | undefined; + +export type { ScalarKind, ScalarType }; + +// Container context carried during evaluation. +export interface RefContainer { + // Root model where identifier lookups begin. + base: ScvdNode; + + // Top-level anchor for the final read (e.g., TCB). + anchor?: ScvdNode | undefined; + + // Accumulated byte offset from the anchor. + offsetBytes?: number | undefined; + + // Final read width in bytes. + widthBytes?: number | undefined; + + // Current ref resolved by the last resolution step (for chaining). + current?: ScvdNode | undefined; + + // Most recent resolved member reference (child). + member?: ScvdNode | undefined; + + // Most recent numeric index for array access (e.g., arr[3]). + index?: number | undefined; + + /** + * Scalar type of the current value (if known). + * Always present but may be undefined. + */ + valueType: ScalarType | undefined; +} diff --git a/src/views/component-viewer/resolver.ts b/src/views/component-viewer/resolver.ts new file mode 100644 index 00000000..a515416f --- /dev/null +++ b/src/views/component-viewer/resolver.ts @@ -0,0 +1,141 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from './model/scvd-node'; +import { ScvdComponentViewer } from './model/scvd-component-viewer'; +import { ScvdTypedef } from './model/scvd-typedef'; +import { ScvdTypesCache } from './scvd-types-cache'; + +export enum ResolveType { + localType = 'localType', + localMember = 'localMember', + targetType = 'targetType', +} + +export type ResolveSymbolCb = ( + name: string, + resolveType: ResolveType, + scvdObject?: ScvdNode +) => ScvdNode | undefined; + +export class Resolver { + private _model: ScvdComponentViewer | undefined; + private _typesCache: ScvdTypesCache | undefined; + + constructor( + model: ScvdComponentViewer, + ) { + this.model = model; + } + + protected get model(): ScvdComponentViewer | undefined { + return this._model; + } + private set model(value: ScvdComponentViewer | undefined) { + this._model = value; + } + + public get typesCache(): ScvdTypesCache | undefined { + return this._typesCache; + } + + private createTypesCache() { + if (this.model === undefined) { + return; + } + this._typesCache = new ScvdTypesCache(this.model); + this._typesCache.createCache(); + } + + private resolveLocalType(name: string): ScvdNode | undefined { + const typeItem = this.typesCache?.findTypeByName(name); + if (typeItem !== undefined && typeItem instanceof ScvdTypedef) { + return typeItem; + } + return undefined; + } + + private resolveLocalMember(name: string, scvdObject?: ScvdNode): ScvdNode | undefined { + if (scvdObject === undefined) { + return undefined; + } + const memberItem = scvdObject.getSymbol(name); + if (memberItem !== undefined) { + return memberItem; + } + return undefined; + } + + private resolveTargetType(_name: string): ScvdNode | undefined { + // resolve using debugger interface + console.log(` Resolving target symbol: ${_name}`); + return undefined; + } + + public resolveSymbolCb( + name: string, + resolveType: ResolveType, + scvdObject?: ScvdNode + ): ScvdNode | undefined { + switch (resolveType) { + case ResolveType.localType: + return this.resolveLocalType(name); + case ResolveType.targetType: + return this.resolveTargetType(name); + case ResolveType.localMember: + return this.resolveLocalMember(name, scvdObject); + default: + return undefined; + } + } + + private resolveRecursive(item: ScvdNode, resolveFunc: ResolveSymbolCb): boolean { + /*const resolvedItem =*/ item.resolveAndLink(resolveFunc); + // if (resolvedItem) { + // console.log('Resolved item:', item.getDisplayLabel()); + // } + + item.forEach(child => { + this.resolveRecursive(child, resolveFunc); + }); + + return true; + } + + private resolveTypes(): boolean { + const model = this.model; + const typesCache = this.typesCache; + if (model === undefined || typesCache === undefined) { + return false; + } + + this.resolveRecursive(model, this.resolveSymbolCb.bind(this)); + + return true; + } + + public resolve(): boolean { + const model = this.model; + if ( model === undefined) { + return false; + } + + this.createTypesCache(); + this.resolveTypes(); + + return true; + } +} diff --git a/src/views/component-viewer/scvd-debug-target.ts b/src/views/component-viewer/scvd-debug-target.ts new file mode 100644 index 00000000..6cab603d --- /dev/null +++ b/src/views/component-viewer/scvd-debug-target.ts @@ -0,0 +1,342 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentViewerTargetAccess } from './component-viewer-target-access'; +import { GDBTargetDebugSession } from '../../debug-session/gdbtarget-debug-session'; +import { GDBTargetDebugTracker } from '../../debug-session'; + +const REGISTER_GDB_ENTRIES: Array<[string, string]> = [ + // Core + ['R0', 'r0'], ['R1', 'r1'], ['R2', 'r2'], ['R3', 'r3'], + ['R4', 'r4'], ['R5', 'r5'], ['R6', 'r6'], ['R7', 'r7'], + ['R8', 'r8'], ['R9', 'r9'], ['R10', 'r10'], ['R11', 'r11'], + ['R12', 'r12'], ['R13', 'r13'], ['R14', 'r14'], ['R15', 'r15'], + ['PSP', 'psp'], ['MSP', 'msp'], ['XPSR', 'xpsr'], ['PRIMASK', 'primask'], + ['BASEPRI', 'basepri'], ['FAULTMASK', 'faultmask'], ['CONTROL', 'control'], + + // Armv8-M Secure/Non-secure additions + ['MSP_NS', 'msp_ns'], ['PSP_NS', 'psp_ns'], ['MSP_S', 'msp_s'], ['PSP_S', 'psp_s'], + ['MSPLIM_S', 'msplim_s'], ['PSPLIM_S', 'psplim_s'], ['MSPLIM_NS', 'msplim_ns'], ['PSPLIM_NS', 'psplim_ns'], + ['SYSREGS_S', 'sysregs_s'], ['SYSREGS_NS', 'sysregs_ns'], ['SECURITY', 'security'], + ['PRIMASK_S', 'primask_s'], ['BASEPRI_S', 'basepri_s'], ['FAULTMASK_S', 'faultmask_s'], ['CONTROL_S', 'control_s'], + ['PRIMASK_NS', 'primask_ns'], ['BASEPRI_NS', 'basepri_ns'], ['FAULTMASK_NS', 'faultmask_ns'], ['CONTROL_NS', 'control_ns'], +]; + +// Full mapping of register names to the GDB names used when requesting them. +const REGISTER_GDB_MAP = new Map(REGISTER_GDB_ENTRIES); + +function normalize(name: string): string { + return name.trim().toUpperCase(); +} + +function toUint32(value: number | bigint): number | bigint { + if (typeof value === 'bigint') { + return value & 0xFFFFFFFFn; + } + return value >>> 0; +} + +function isLikelyBase64(data: string): boolean { + const trimmed = data.trim(); + if (trimmed.length === 0 || trimmed.length % 4 === 1) { + return false; + } + if (/[^A-Za-z0-9+/=]/.test(trimmed)) { + return false; + } + return true; +} + +export function gdbNameFor(name: string): string | undefined { + return REGISTER_GDB_MAP.get(normalize(name)); +} + +export interface MemberInfo { + name: string; + size: number; + offset: number; +} + +export interface SymbolInfo { + name: string; + address: number; + size?: number; + member?: MemberInfo[]; +} + +export class ScvdDebugTarget { + private activeSession: GDBTargetDebugSession | undefined; + private targetAccess: ComponentViewerTargetAccess; + private debugTracker: GDBTargetDebugTracker | undefined; + private isTargetRunning: boolean = false; + + constructor( + ) { + this.targetAccess = new ComponentViewerTargetAccess(); + } + + // ------------- Interface to debugger ----------------- + public init(session: GDBTargetDebugSession, tracker: GDBTargetDebugTracker): void { + this.activeSession = session; + this.targetAccess.setActiveSession(session); + this.debugTracker = tracker; + this.subscribeToTargetRunningState(this.debugTracker); + } + + protected async subscribeToTargetRunningState(debugTracker: GDBTargetDebugTracker): Promise { + debugTracker.onContinued(async (event) => { + if (!this.activeSession || event.session.session.id !== this.activeSession.session.id) { + return; + } + this.isTargetRunning = true; + }); + + debugTracker.onStopped(async (event) => { + if (!this.activeSession || event.session.session.id !== this.activeSession.session.id) { + return; + } + this.isTargetRunning = false; + }); + } + + public async getSymbolInfo(symbol: string): Promise { + if (symbol === undefined) { + return undefined; + } + if (!this.activeSession) { + return undefined; + } + + const symbolName = symbol; + const symbolAddressStr = await this.targetAccess.evaluateSymbolAddress(symbol); + if (symbolAddressStr !== undefined) { + const addr = parseInt(symbolAddressStr as unknown as string, 16); + if (Number.isFinite(addr)) { + const symbolInfo: SymbolInfo = { + name: symbolName, + address: addr + }; + return symbolInfo; + } + console.error(`getSymbolInfo: could not parse address for ${symbolName}:`, symbolAddressStr); + } + return undefined; + } + + public async findSymbolNameAtAddress(address: number): Promise { + if (!this.activeSession) { + return Promise.resolve(undefined); + } + + try { + return await this.targetAccess.evaluateSymbolName(address.toString()); + } catch (error: unknown) { + console.error(`findSymbolNameAtAddress failed for ${address}:`, error); + return undefined; + } + } + + public async findSymbolContextAtAddress(address: number | bigint): Promise { + // Return file/line context for an address when the adapter supports it. + if (!this.activeSession) { + return Promise.resolve(undefined); + } + + try { + return await this.targetAccess.evaluateSymbolContext(address.toString()); + } catch (error: unknown) { + console.error(`findSymbolContextAtAddress failed for ${address}:`, error); + return undefined; + } + } + + public async getNumArrayElements(symbol: string): Promise { + if (symbol === undefined) { + return undefined; + } + // No active session: return undefined. + if (!this.activeSession) { + return undefined; + } + return await this.targetAccess.evaluateNumberOfArrayElements(symbol); + } + + public async getTargetIsRunning(): Promise { + if (!this.activeSession) { + return false; + } + return this.isTargetRunning; + } + + public async findSymbolAddress(symbol: string): Promise { + const symbolInfo = await this.getSymbolInfo(symbol); + if (symbolInfo === undefined) { + return undefined; + } + return symbolInfo.address; + } + + public async getSymbolSize(symbol: string): Promise { + if (!symbol) { + return undefined; + } + if (!this.activeSession) { + return undefined; + } + + // real session: ask debugger via target access + const size = await this.targetAccess.evaluateSymbolSize(symbol); + if (typeof size === 'number' && size >= 0) { + return size; + } + return undefined; + } + + /** + * Decode a (possibly unpadded) base64 string from GDB into bytes. + */ + public decodeGdbData(data: string): Uint8Array | undefined { + // Fix missing padding: base64 length must be a multiple of 4 + const padLength = (4 - (data.length % 4)) % 4; + const padded = data + '='.repeat(padLength); + + // Node.js or environments with Buffer + if (typeof Buffer !== 'undefined') { + return Uint8Array.from(Buffer.from(padded, 'base64')); + } + + // Browser / Deno / modern runtimes with atob + if (typeof atob === 'function') { + const binary = atob(padded); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + // eslint-disable-next-line security/detect-object-injection -- false positive: safe indexed copy from string to typed array + bytes[i] = binary.charCodeAt(i) & 0xff; + } + return bytes; + } + + console.error('ScvdDebugTarget.decodeGdbData: no base64 decoder available in this environment'); + return undefined; + } + + public async readMemory(address: number | bigint, size: number): Promise { + if (!this.activeSession) { + return undefined; + } + + const dataAsString = await this.targetAccess.evaluateMemory(address.toString(), size, 0); + if (typeof dataAsString !== 'string') { + return undefined; + } + // if data is returned as error message string + if (dataAsString.startsWith('Unable')) { + return undefined; + } + if (!isLikelyBase64(dataAsString)) { + console.error(`ScvdDebugTarget.readMemory: invalid base64 data for address ${address.toString()}`); + return undefined; + } + // Convert String data to Uint8Array + const byteArray = this.decodeGdbData(dataAsString); + if (byteArray === undefined) { + return undefined; + } + + return byteArray.length === size ? byteArray : undefined; + } + + public readUint8ArrayStrFromPointer(address: number | bigint, bytesPerChar: number, maxLength: number): Promise { + if (address === 0 || address === 0n) { + return Promise.resolve(undefined); + } + return this.readMemory(address, maxLength * bytesPerChar); + } + + public async calculateMemoryUsage(startAddress: number, size: number, FillPattern: number, MagicValue: number): Promise { + const memData = await this.readMemory(startAddress, size); + if (memData !== undefined) { + let usedBytes = 0; + const fillPatternBytes = new Uint8Array(4); + const magicValueBytes = new Uint8Array(4); + // Use FillPattern for the fill pattern bytes (little-endian) + fillPatternBytes[0] = (FillPattern & 0xFF); + fillPatternBytes[1] = (FillPattern >> 8) & 0xFF; + fillPatternBytes[2] = (FillPattern >> 16) & 0xFF; + fillPatternBytes[3] = (FillPattern >> 24) & 0xFF; + // Use MagicValue for the magic value bytes (little-endian) + magicValueBytes[0] = (MagicValue & 0xFF); + magicValueBytes[1] = (MagicValue >> 8) & 0xFF; + magicValueBytes[2] = (MagicValue >> 16) & 0xFF; + magicValueBytes[3] = (MagicValue >> 24) & 0xFF; + + for (let i = 0; i < memData.length; i += 4) { + const chunk = memData.subarray(i, i + 4); + const matchesFill = chunk.every((byte, idx) => byte === fillPatternBytes.at(idx)); + const matchesMagic = matchesFill || chunk.every((byte, idx) => byte === magicValueBytes.at(idx)); + if (!matchesMagic) { + usedBytes += chunk.length; + } + } + + const usedPercent = Math.floor((usedBytes / size) * 100) & 0x1FF; + let result = usedBytes & 0xFFFFF; // bits 0..19 + result |= (usedPercent << 20); // bits 20..28 + + // Check for overflow (MagicValue overwritten) + let overflow = true; + const tailStart = Math.max(0, memData.length - 4); + for (let i = tailStart; i < memData.length; i++) { + const expected = magicValueBytes.at(i - tailStart); + if (memData.at(i) !== expected) { + overflow = false; + break; + } + } + if (overflow) { + result |= (1 << 31); // set overflow bit + } + + return result; + } + + return undefined; + } + + + public async readRegister(name: string): Promise { + if (name === undefined) { + return undefined; + } + + const gdbName = gdbNameFor(name); + if (gdbName === undefined) { + console.error(`ScvdDebugTarget: readRegister: could not find GDB name for register: ${name}`); + return undefined; + } + // Read register value via target access + const value = await this.targetAccess.evaluateRegisterValue(gdbName); + if (value === undefined) { + return undefined; + } + // Convert to number or bigint and return as uint32 + const numericValue = Number(value); + return toUint32(numericValue); + } +} + +// Test-only helpers +export const __test__ = { toUint32 }; diff --git a/src/views/component-viewer/scvd-eval-context.ts b/src/views/component-viewer/scvd-eval-context.ts new file mode 100644 index 00000000..226bd0ae --- /dev/null +++ b/src/views/component-viewer/scvd-eval-context.ts @@ -0,0 +1,114 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../debug-session'; +import { MemoryHost } from './data-host/memory-host'; +import { RegisterHost } from './data-host/register-host'; +import { EvalContext } from './parser-evaluator/evaluator'; +import { ScvdNode } from './model/scvd-node'; +import { ScvdComponentViewer } from './model/scvd-component-viewer'; +import { ScvdFormatSpecifier } from './model/scvd-format-specifier'; +import { ScvdDebugTarget } from './scvd-debug-target'; +import { ScvdEvalInterface } from './scvd-eval-interface'; + +export interface ExecutionContext { + memoryHost: MemoryHost; + registerHost: RegisterHost; + evalContext: EvalContext; + debugTarget: ScvdDebugTarget; +} + + +export class ScvdEvalContext { + private _ctx: EvalContext; + private _evalHost: ScvdEvalInterface; + private _memoryHost: MemoryHost; + private _registerHost: RegisterHost; + private _debugTarget: ScvdDebugTarget; + private _formatSpecifier: ScvdFormatSpecifier; + private _model: ScvdComponentViewer; + + constructor( + model: ScvdComponentViewer + ) { + this._model = model; + this._memoryHost = new MemoryHost(); + this._registerHost = new RegisterHost(); + this._debugTarget = new ScvdDebugTarget(); + this._formatSpecifier = new ScvdFormatSpecifier(); + this._evalHost = new ScvdEvalInterface(this._memoryHost, this._registerHost, this._debugTarget, this._formatSpecifier); + const outItem = this.getOutItem(); + if (outItem === undefined) { + throw new Error('SCVD EvalContext: No output item defined'); + } + + this._ctx = new EvalContext({ + data: this._evalHost, // host for model lookup + data access + intrinsics + container: outItem, // ScvdNode root for symbol resolution + }); + } + + private get model(): ScvdComponentViewer { + return this._model !== undefined ? this._model : (() => { + throw new Error('SCVD EvalContext: Model not initialized'); + })(); + } + + private get memoryHost(): MemoryHost { + return this._memoryHost !== undefined ? this._memoryHost : (() => { + throw new Error('SCVD EvalContext: MemoryHost not initialized'); + })(); + } + + private get registerHost(): RegisterHost { + return this._registerHost !== undefined ? this._registerHost : (() => { + throw new Error('SCVD EvalContext: RegisterHost not initialized'); + })(); + } + + private get ctx(): EvalContext { + return this._ctx !== undefined ? this._ctx : (() => { + throw new Error('SCVD EvalContext: EvalContext not initialized'); + })(); + } + + public getExecutionContext(): ExecutionContext { + return { + memoryHost: this.memoryHost, + registerHost: this.registerHost, + evalContext: this.ctx, + debugTarget: this._debugTarget !== undefined ? this._debugTarget : (() => { + throw new Error('SCVD EvalContext: DebugTarget not initialized'); + })(), + }; + } + + public getOutItem(): ScvdNode | undefined { + const objects = this.model.objects; + if (objects === undefined) { + return undefined; + } + if (objects.objects.length > 0) { + const object = objects.objects[0]; + return object; + } + return undefined; + } + + public init(debugSession: GDBTargetDebugSession, debugTracker: GDBTargetDebugTracker): void { + this._debugTarget.init(debugSession, debugTracker); + } +} diff --git a/src/views/component-viewer/scvd-eval-interface.ts b/src/views/component-viewer/scvd-eval-interface.ts new file mode 100644 index 00000000..13f867dc --- /dev/null +++ b/src/views/component-viewer/scvd-eval-interface.ts @@ -0,0 +1,462 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DataAccessHost, EvalValue, ModelHost, RefContainer, ScalarType } from './parser-evaluator/model-host'; +import type { IntrinsicProvider } from './parser-evaluator/intrinsics'; +import { ScvdNode } from './model/scvd-node'; +import { MemoryHost } from './data-host/memory-host'; +import { RegisterHost } from './data-host/register-host'; +import { ScvdDebugTarget } from './scvd-debug-target'; +import { FormatSegment } from './parser-evaluator/parser'; +import { FormatTypeInfo, ScvdFormatSpecifier } from './model/scvd-format-specifier'; +import { ScvdMember } from './model/scvd-member'; + +export class ScvdEvalInterface implements ModelHost, DataAccessHost, IntrinsicProvider { + private _registerCache: RegisterHost; + private _memHost: MemoryHost; + private _debugTarget: ScvdDebugTarget; + private _formatSpecifier: ScvdFormatSpecifier; + + constructor( + memHost: MemoryHost, + regHost: RegisterHost, + debugTarget: ScvdDebugTarget, + formatterSpecifier: ScvdFormatSpecifier + ) { + this._memHost = memHost; + this._registerCache = regHost; + this._debugTarget = debugTarget; + this._formatSpecifier = formatterSpecifier; + } + + private get registerHost(): RegisterHost { + return this._registerCache; + } + + private get memHost(): MemoryHost { + return this._memHost; + } + + private get debugTarget(): ScvdDebugTarget { + return this._debugTarget; + } + + private get formatSpecifier(): ScvdFormatSpecifier { + return this._formatSpecifier; + } + + private normalizeScalarType(raw: string | ScalarType | undefined): ScalarType | undefined { + if (!raw) { + return undefined; + } + if (typeof raw !== 'string') { + return raw; + } + + const trimmed = raw.trim(); + const lower = trimmed.toLowerCase(); + let kind: ScalarType['kind'] = 'int'; + if (lower.includes('uint') || lower.includes('unsigned')) { + kind = 'uint'; + } else if (lower.includes('float') || lower.includes('double')) { + kind = 'float'; + } + + const out: ScalarType = { kind, name: trimmed }; + const bits = lower.match(/(8|16|32|64)/); + if (bits) { + out.bits = parseInt(bits[1], 10); + } + return out; + } + + private async getScalarInfo(container: RefContainer): Promise { + const currentRef = container.current ?? container.base; + + // Prefer explicit scalar type + const rawType = await this.getValueType(container); + const scalar = this.normalizeScalarType(rawType); + const kind = scalar?.kind ?? 'unknown'; + + // Derive element width and array-ness + const arrayCount = typeof currentRef?.getArraySize === 'function' ? await currentRef.getArraySize() : undefined; + if (currentRef?.name === '_addr') { + return { kind, bits: 32, widthBytes: 4 }; + } + if (arrayCount && arrayCount > 1) { + return { kind, bits: 32, widthBytes: 4 }; + } + + // Determine element width: prefer target size, then container hint, then byte-width helper. + let widthBytes: number | undefined = currentRef?.getTargetSize?.(); + if ((!widthBytes || widthBytes <= 0) && container.widthBytes) { + widthBytes = container.widthBytes; + } + if ((!widthBytes || widthBytes <= 0) && typeof this.getByteWidth === 'function' && currentRef) { + const w = await this.getByteWidth(currentRef); + if (typeof w === 'number' && w > 0) { + widthBytes = w; + } + } + + // Only pad numbers. + let bits: number | undefined; + const isScalar = kind === 'int' || kind === 'uint' || kind === 'float'; + + if (isScalar) { + bits = scalar?.bits; + if (bits === undefined && widthBytes && widthBytes > 0) { + bits = widthBytes * 8; + } + if (bits === undefined) { + bits = 32; + } + if (bits > 64) { + bits = 64; + } + } else { + bits = 32; // default padding for unknown/non-scalar + } + + const info: FormatTypeInfo & { widthBytes?: number } = { kind }; + if (bits !== undefined) { + info.bits = bits; + } + if (widthBytes !== undefined) { + info.widthBytes = widthBytes; + } + return info; + } + + private async readBytesFromPointer(address: number, length: number): Promise { + if (!Number.isFinite(address) || length <= 0) { + return undefined; + } + return this.debugTarget.readMemory(address >>> 0, length); + } + + private normalizeName(name: string | undefined): string | undefined { + const trimmed = name?.trim(); + return trimmed && trimmed.length > 0 ? trimmed : undefined; + } + + private async findSymbolAddressNormalized(name: string | undefined): Promise { + const normalized = this.normalizeName(name); + if (!normalized) { + return undefined; + } + return this.debugTarget.findSymbolAddress(normalized); + } + + // ---------------- Host Interface: model + data access ---------------- + public async getSymbolRef(container: RefContainer, name: string, _forWrite?: boolean): Promise { + return container.base.getSymbol?.(name); + } + + public async getMemberRef(container: RefContainer, property: string, _forWrite?: boolean): Promise { + const base = container.current; + return base?.getMember(property); + } + + public async resolveColonPath(_container: RefContainer, _parts: string[]): Promise { + return undefined; + } + + public async getElementRef(ref: ScvdNode): Promise { + return ref.getElementRef(); + } + + // Optional helper used by the evaluator + // Returns the byte width of a ref (scalars, structs, arrays – host-defined). + // getTargetSize, getTypeSize, getVirtualSize + public async getByteWidth(ref: ScvdNode): Promise { + const isPointer = ref.getIsPointer(); + if (isPointer) { + return 4; // pointer size + } + const size = ref.getTargetSize(); + const numOfElements = await ref.getArraySize(); + + if (size !== undefined) { + return numOfElements ? size * numOfElements : size; + } + console.error(`ScvdEvalInterface.getByteWidth: size undefined for ${ref.getDisplayLabel()}`); + return undefined; + } + + /* bytes per element (including any padding/alignment inside the array layout). + Stride only answers: “how far do I move to get from element i to i+1?” + */ + public async getElementStride(ref: ScvdNode): Promise { + const isPointer = ref.getIsPointer(); + if (isPointer) { + return 4; // pointer size + } + const stride = ref.getVirtualSize(); + if (stride !== undefined) { + return stride; + } + const size = ref.getTargetSize(); + if (size !== undefined) { + return size; + } + console.error(`ScvdEvalInterface.getElementStride: size/stride undefined for ${ref.getDisplayLabel()}`); + return 0; + } + + public async getMemberOffset(_base: ScvdNode, member: ScvdNode): Promise { + const offset = await member.getMemberOffset(); + if (offset === undefined) { + console.error(`ScvdEvalInterface.getMemberOffset: offset undefined for ${member.getDisplayLabel()}`); + return undefined; + } + return offset; + } + + public async getValueType(container: RefContainer): Promise { + const base = container.current; + const type = base?.getValueType(); + if (type !== undefined) { + return type; + } + return undefined; + } + + /* ---------------- Read/Write via caches ---------------- */ + public async readValue(container: RefContainer): Promise { + try { + const value = await this.memHost.readValue(container); + return value as EvalValue; + } catch (e) { + console.error(`ScvdEvalInterface.readValue: exception for container with base=${container.base.getDisplayLabel()}: ${e}`); + return undefined; + } + } + + public async writeValue(container: RefContainer, value: EvalValue): Promise { + try { + await this.memHost.writeValue(container, value); + return value; + } catch (e) { + console.error(`ScvdEvalInterface.writeValue: exception for container with base=${container.base.getDisplayLabel()}: ${e}`); + return undefined; + } + } + + /* ---------------- Intrinsics ---------------- */ + + public async __FindSymbol(symbolName: string): Promise { + return this.findSymbolAddressNormalized(symbolName); + } + + public async __GetRegVal(regName: string): Promise { + const normalized = this.normalizeName(regName); + if (!normalized) { + return undefined; + } + const cachedRegVal = this.registerHost.read(normalized); + if (cachedRegVal === undefined) { + const value = await this.debugTarget.readRegister(normalized); + if (value === undefined) { + return undefined; + } + this.registerHost.write(normalized, value); + return value; + } + return cachedRegVal; + } + + public async __Symbol_exists(symbol: string): Promise { + const found = await this.findSymbolAddressNormalized(symbol); + return found !== undefined ? 1 : 0; + } + + /* Returns + A packed 32-bit integer value that indicates memory usage in bytes, in percent, and memory overflow: + Bit 0..19 Used memory in Bytes (how many bytes of FillPattern are overwritten) + Bit 20..28 Used memory in percent (how many percent of FillPattern are overwritten) + Bit 31 Memory overflow (MagicValue is overwritten) + */ + public async __CalcMemUsed(stackAddress: number, stackSize: number, fillPattern: number, magicValue: number): Promise { + const memUsed = await this.debugTarget.calculateMemoryUsage( + stackAddress >>> 0, + stackSize >>> 0, + fillPattern >>> 0, + magicValue >>> 0 + ); + return memUsed; + } + + // Number of elements of an array defined by a symbol in user application. + public async __size_of(symbol: string): Promise { + const sizeBytes = await this.debugTarget.getSymbolSize(symbol); + if (sizeBytes !== undefined) { + return sizeBytes; + } + // Legacy fallback: try array element count if size is unavailable + const arrayElements = await this.debugTarget.getNumArrayElements(symbol); + if (arrayElements !== undefined) { + return arrayElements; + } + return undefined; + } + + public async __Offset_of(container: RefContainer, typedefMember: string): Promise { + const memberRef = container.base.getMember(typedefMember); + if (memberRef) { + const offset = await memberRef.getMemberOffset(); + return offset; + } + return undefined; + } + + public async __Running(): Promise { + const isRunning = await this.debugTarget.getTargetIsRunning(); + return isRunning ? 1 : 0; + } + + public async _count(container: RefContainer): Promise { + const base = container.current; + const name = base?.name; + if (name !== undefined) { + const count = this.memHost.getArrayElementCount(name); // TOIMPL: this works only for , must add for + return count; + } + return undefined; + } + + public async _addr(container: RefContainer): Promise { + const base = container.current; + const name = base?.name; + const index = container.index ?? 0; + if (name !== undefined) { + const addr = this.memHost.getElementTargetBase(name, index); + return addr; + } + return undefined; + } + + public async formatPrintf(spec: FormatSegment['spec'], value: EvalValue, container: RefContainer): Promise { + const base = container.current; + const typeInfo = await this.getScalarInfo(container); + + const toNumeric = (v: unknown): number | bigint => { + if (typeof v === 'number' || typeof v === 'bigint') { + return v; + } + if (typeof v === 'boolean') { + return v ? 1 : 0; + } + if (typeof v === 'string') { + const n = Number(v); + return Number.isFinite(n) ? n : NaN; + } + return NaN; + }; + + switch (spec) { + case 'C': { + // TOIMPL: include file/line context when targetAccess exposes it (e.g., GDB "info line *addr"). + const addr = typeof value === 'number' ? value : undefined; + if (addr === undefined) { + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + const context = await this.debugTarget.findSymbolContextAtAddress(addr); + if (context !== undefined) { + return this.formatSpecifier.format(spec, context, { typeInfo, allowUnknownSpec: true }); + } + const name = await this.debugTarget.findSymbolNameAtAddress(addr); + return this.formatSpecifier.format(spec, name ?? addr, { typeInfo, allowUnknownSpec: true }); + } + case 'S': { + const addr = typeof value === 'number' ? value : undefined; + if (addr === undefined) { + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + const name = await this.debugTarget.findSymbolNameAtAddress(addr); + return this.formatSpecifier.format(spec, name ?? addr, { typeInfo, allowUnknownSpec: true }); + } + case 'E': { + const memberItem = base?.castToDerived(ScvdMember); + const enumItem = typeof value === 'number' ? await memberItem?.getEnum(value) : undefined; + const enumStr = await enumItem?.getGuiName(); + const opts: { typeInfo: FormatTypeInfo; allowUnknownSpec: true; enumText?: string } = { typeInfo, allowUnknownSpec: true }; + if (enumStr !== undefined) { + opts.enumText = enumStr; + } + return this.formatSpecifier.format(spec, value, opts); + } + case 'I': { + if (value instanceof Uint8Array) { + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + if (typeof value === 'number') { + const buf = await this.readBytesFromPointer(value, 4); + return this.formatSpecifier.format(spec, buf ?? value, { typeInfo, allowUnknownSpec: true }); + } + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + case 'J': { + if (value instanceof Uint8Array) { + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + if (typeof value === 'number') { + const buf = await this.readBytesFromPointer(value, 16); + return this.formatSpecifier.format(spec, buf ?? value, { typeInfo, allowUnknownSpec: true }); + } + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + case 'x': { + let n = toNumeric(value); + if (typeof n === 'number') { + n = Math.trunc(n); + } + return this.formatSpecifier.format(spec, n, { typeInfo, allowUnknownSpec: true }); + } + case 'N': { + if (typeof value === 'number' && Number.isInteger(value)) { + const data = await this.debugTarget.readUint8ArrayStrFromPointer(value, 1, 260 - 4); + if (data !== undefined) { + return this.formatSpecifier.format(spec, data, { typeInfo, allowUnknownSpec: true }); + } + } + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + case 'M': { + if (value instanceof Uint8Array) { + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + if (typeof value === 'number') { + const buf = await this.readBytesFromPointer(value, 6); + return this.formatSpecifier.format(spec, buf ?? value, { typeInfo, allowUnknownSpec: true }); + } + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + case 'U': { + if (typeof value === 'number' && Number.isInteger(value)) { + const data = await this.debugTarget.readUint8ArrayStrFromPointer(value, 2, 260 - 4); + if (data !== undefined) { + return this.formatSpecifier.format(spec, data, { typeInfo, allowUnknownSpec: true }); + } + } + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + default: { + return this.formatSpecifier.format(spec, value, { typeInfo, allowUnknownSpec: true }); + } + } + } +} diff --git a/src/views/component-viewer/scvd-gui-tree.ts b/src/views/component-viewer/scvd-gui-tree.ts new file mode 100644 index 00000000..26651608 --- /dev/null +++ b/src/views/component-viewer/scvd-gui-tree.ts @@ -0,0 +1,247 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdGuiInterface } from './model/scvd-gui-interface'; + +export class ScvdGuiTree implements ScvdGuiInterface { + private _parent: ScvdGuiTree | undefined; + private _nodeId: string; + private _name: string | undefined; + private _value: string | undefined; + private _children: ScvdGuiTree[] = []; + private _childIndex: Map = new Map(); + // Per-update counter for duplicate keys to generate stable suffixed keys (key, key#1, key#2, ...) + private _keyCursor: Map = new Map(); + private _key: string | undefined; + // Marks when this node was last visited during a reconciliation pass. + // If the node is not seen in the current epoch it is pruned in finalizeUpdate(). + private _seenEpoch = 0; + private _isPrint: boolean = false; + private static readonly baseNodeId = 'ScvdGuiTree'; + private static idCnt: number = 0; + // Monotonic counter for reconciliation passes. Increments at beginUpdate() and is compared against _seenEpoch. + private static _epoch: number = 0; + + constructor( + parent: ScvdGuiTree | undefined, + nodeId?: string, + ) { + this._parent = parent; + if (parent) { + parent.addChild(this); + } + const baseId = nodeId ?? ScvdGuiTree.baseNodeId; + this._nodeId = `${baseId}_${ScvdGuiTree.idCnt++}`; + } + + public beginUpdate(): number { + const nextEpoch = ++ScvdGuiTree._epoch; + this.markSeen(nextEpoch); + return nextEpoch; + } + + public finalizeUpdate(updateEpoch: number): void { + const survivors: ScvdGuiTree[] = []; + for (const child of this._children) { + if (child.seenEpoch === updateEpoch) { + survivors.push(child); + } else if (child.key !== undefined) { + this._childIndex.delete(child.key); + child._parent = undefined; + } + } + this._children = survivors; + for (const child of survivors) { + child.finalizeUpdate(updateEpoch); + } + } + + private markSeen(updateEpoch: number): void { + this.seenEpoch = updateEpoch; + this._keyCursor = new Map(); + } + + private bumpOrder(child: ScvdGuiTree): void { + const index = this._children.indexOf(child); + if (index >= 0 && index !== this._children.length - 1) { + this._children.splice(index, 1); + this._children.push(child); + } + } + + public getOrCreateChild(key: string, nodeId?: string): ScvdGuiTree { + const updateEpoch = ScvdGuiTree.epoch; + try { + const index = this._keyCursor.get(key) ?? 0; + this._keyCursor.set(key, index + 1); + const effectiveKey = index === 0 ? key : `${key}#${index}`; + + const existing = this._childIndex.get(effectiveKey); + if (existing) { + existing.markSeen(updateEpoch); + this.bumpOrder(existing); + return existing; + } + + const child = new ScvdGuiTree(this, nodeId); + child.key = effectiveKey; + child.markSeen(updateEpoch); + this._childIndex.set(effectiveKey, child); + return child; + } catch (err) { + console.error(`Failed to create GUI child "${key}" under "${this.path}":`, err); + const fallback = new ScvdGuiTree(this, nodeId); + fallback.key = `${key}#fallback`; + fallback.markSeen(updateEpoch); + return fallback; + } + } + + public get parent(): ScvdGuiTree | undefined { + return this._parent; + } + + public get childIndex(): ReadonlyMap { + return this._childIndex; + } + + public get keyCursor(): ReadonlyMap { + return this._keyCursor; + } + + public get key(): string | undefined { + return this._key; + } + protected set key(value: string | undefined) { + this._key = value; + } + + public get seenEpoch(): number { + return this._seenEpoch; + } + protected set seenEpoch(value: number) { + this._seenEpoch = value; + } + + public static get epoch(): number { + return ScvdGuiTree._epoch; + } + public static set epoch(value: number) { + ScvdGuiTree._epoch = value; + } + + // Depth-first iterator from this node up through its parents (self first). + private *ancestors(): Iterable { + yield this; + if (this.parent) { + yield* this.parent.ancestors(); + } + } + + private get path(): string { + const parts: string[] = []; + for (const node of this.ancestors()) { + parts.push(node.key ?? node.name ?? node.nodeId); + } + return parts.reverse().join(' > '); + } + + public get nodeId(): string { + return this._nodeId; + } + + public clear(): void { + this._children = []; + this._childIndex.clear(); + } + + public get isPrint(): boolean { + return this._isPrint; + } + public set isPrint(value: boolean) { + this._isPrint = value; + } + + private set name(value: string | undefined) { + this._name = value; + } + public get name(): string | undefined { + return this._name; + } + + public get value(): string | undefined { + return this._value; + } + + public get children(): ScvdGuiTree[] { + return this._children; + } + + protected addChild(child: ScvdGuiTree): void { + this._children.push(child); + } + + public detach(): void { + if (!this._parent) { + return; + } + this._parent._children = this._parent._children.filter(child => child !== this); + if (this._key !== undefined) { + this._parent._childIndex.delete(this._key); + } + this._parent = undefined; + } + + public setGuiName(value: string | undefined) { + this._name = value; + } + + public setGuiValue(value: string | undefined) { + this._value = value; + } + + // -------- ScvdGuiInterface methods -------- + public getGuiEntry(): { name: string | undefined; value: string | undefined } { + return { name: this._name, value: this._value }; + } + + public getGuiChildren(): ScvdGuiInterface[] { + return this.children; + } + + public getGuiName(): string | undefined { + return this.name; + } + + public getGuiValue(): string | undefined { + return this.value; + } + + public getGuiConditionResult(): boolean { + return true; + } + + public getGuiLineInfo(): string | undefined { + return undefined; + } + + public hasGuiChildren(): boolean { + return this._children.length > 0; + } + // -------- ScvdGuiInterface methods -------- + + +} diff --git a/src/views/component-viewer/scvd-types-cache.ts b/src/views/component-viewer/scvd-types-cache.ts new file mode 100644 index 00000000..c6337106 --- /dev/null +++ b/src/views/component-viewer/scvd-types-cache.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from './model/scvd-node'; +import { ScvdComponentViewer } from './model/scvd-component-viewer'; +import { ScvdTypedef } from './model/scvd-typedef'; + + +// https://arm-software.github.io/CMSIS-View/main/elem_component_viewer.html + + +export class ScvdTypesCache { + private _model: ScvdComponentViewer | undefined; + private typesCache: Map = new Map(); + + constructor( + model: ScvdComponentViewer, + ) { + this.model = model; + } + + protected getModel(): ScvdComponentViewer | undefined { + return this._model; + } + private set model(value: ScvdComponentViewer | undefined) { + this._model = value; + } + + public createCache(): void { + const typedefs = this.getModel()?.typedefs; + if ( typedefs === undefined ) { + return; + } + + typedefs.forEach( item => { + if (!(item instanceof ScvdTypedef)) { + return; + } + + const name = item.name; + if (name !== undefined) { + this.typesCache.set(name, item); + } + }); + } + + public findTypeByName(name: string): ScvdTypedef | undefined { + const typesCache = this.typesCache; + if (typesCache === undefined) { + return undefined; + } + const typeItem = typesCache.get(name); + if (typeItem instanceof ScvdTypedef) { + return typeItem; + } + return undefined; + } +} diff --git a/src/views/component-viewer/statement-engine/statement-base.ts b/src/views/component-viewer/statement-engine/statement-base.ts new file mode 100644 index 00000000..568331e4 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-base.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; + + +/** + * Base statement node using an **array** for children. + * - Children are appended as added. + * - `sortChildren()` sorts by line number (ascending) and is **stable** so + * statements on the same line keep the order they were added. + * - `executeStatement()` walks the tree depth-first in current order. + */ +export class StatementBase { + private _parent: StatementBase | undefined; + private _children: StatementBase[] = []; + private _scvdItem: ScvdNode; + private static readonly unnamedPrefix = 'Unnamed'; + + constructor( + item: ScvdNode, parent: StatementBase | undefined + ) { + this._scvdItem = item; + this._parent = parent; + parent?.addChild(this); + } + + public get parent(): StatementBase | undefined { + return this._parent; + } + + public get children(): StatementBase[] { + return this._children; + } + + public get scvdItem(): ScvdNode { + return this._scvdItem; + } + + // Append a child and return it. + public addChild(child: StatementBase): StatementBase | undefined { + if (child !== undefined) { + this._children.push(child); + } + return child; + } + + // Numeric line for this node, derived from underlying item. + public get line(): number { + const lineNo = Number(this.scvdItem.lineNo); + return isNaN(lineNo) ? 0 : lineNo; + } + + /** + * Stable sort by `line`, then recurse into children. + * Uses index tiebreak to guarantee same-line insertion order. + */ + public sortChildren(): void { + if (this._children.length > 1) { + this._children = this._children + .map((child, originalIndex) => ({ child, originalIndex })) + .sort((left, right) => (left.child.line - right.child.line) || (left.originalIndex - right.originalIndex)) + .map(wrapper => wrapper.child); + } + + for (const child of this._children) { + child.sortChildren(); + } + } + + protected getOrCreateGuiChild(guiTree: ScvdGuiTree, guiName: string | undefined, nodeId?: string): ScvdGuiTree { + const key = guiName ?? `${StatementBase.unnamedPrefix}:${this.scvdItem.constructor?.name}:${this.line}`; + return guiTree.getOrCreateChild(key, nodeId); + } + + public async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + + await this.onExecute(executionContext, guiTree); + + for (const child of this.children) { + await child.executeStatement(executionContext, guiTree); + } + } + + // Override in subclasses to perform work for this node. + protected async onExecute(_executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + //console.log(`${this.line}: Executing base: ${await this.scvdItem.getGuiName()}`); + } +} diff --git a/src/views/component-viewer/statement-engine/statement-break.ts b/src/views/component-viewer/statement-engine/statement-break.ts new file mode 100755 index 00000000..80722ec6 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-break.ts @@ -0,0 +1,47 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdBreak } from '../model/scvd-break'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementBreak extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + public override async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + await this.onExecute(executionContext, guiTree); + } + + protected override async onExecute(_executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + const breakItem = this.scvdItem.castToDerived(ScvdBreak); + if (!breakItem) { + console.error(`${this.line}: Executing "break": could not cast to ScvdBreak`); + return; + } + //console.log(`${this.line}: Executing break: ${await this.scvdItem.getGuiName()}`); + } +} diff --git a/src/views/component-viewer/statement-engine/statement-calc.ts b/src/views/component-viewer/statement-engine/statement-calc.ts new file mode 100644 index 00000000..d5560ab2 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-calc.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdCalc } from '../model/scvd-calc'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementCalc extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + protected override async onExecute(_executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + const calcItem = this.scvdItem.castToDerived(ScvdCalc); + if (!calcItem) { + console.error(`${this.line}: Executing "calc": could not cast to ScvdCalc`); + return; + } + //console.log(`${this.line}: Executing calc: ${await this.scvdItem.getGuiName()}`); + + const expressions = calcItem.expression; + for (const expr of expressions) { + await expr.evaluate(); + //const value = await expr.getValue(); + //console.log(`${this.line} Executing "calc": ${expr.expression}, value: ${value}`); + } + } +} diff --git a/src/views/component-viewer/statement-engine/statement-engine.ts b/src/views/component-viewer/statement-engine/statement-engine.ts new file mode 100644 index 00000000..d235e171 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-engine.ts @@ -0,0 +1,216 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdComponentViewer } from '../model/scvd-component-viewer'; +import { ScvdBreak } from '../model/scvd-break'; +import { ScvdCalc } from '../model/scvd-calc'; +import { ScvdItem } from '../model/scvd-item'; +import { ScvdList } from '../model/scvd-list'; +import { ScvdListOut } from '../model/scvd-list-out'; +import { ScvdObject } from '../model/scvd-object'; +import { ScvdOut } from '../model/scvd-out'; +import { ScvdPrint } from '../model/scvd-print'; +import { ScvdRead } from '../model/scvd-read'; +import { ScvdReadList } from '../model/scvd-readlist'; +import { ScvdVar } from '../model/scvd-var'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; +import { StatementBreak } from './statement-break'; +import { StatementCalc } from './statement-calc'; +import { StatementItem } from './statement-item'; +import { StatementList } from './statement-list'; +import { StatementListOut } from './statement-list-out'; +import { StatementObject } from './statement-object'; +import { StatementOut } from './statement-out'; +import { StatementPrint } from './statement-print'; +import { StatementRead } from './statement-read'; +import { StatementReadList } from './statement-readList'; +import { StatementVar } from './statement-var'; + + +export class StatementEngine { + private _model: ScvdComponentViewer; + private _statementTree: StatementBase | undefined; + private _executionContext: ExecutionContext; + + constructor( + model: ScvdComponentViewer, + executionContext: ExecutionContext + ) { + this._model = model; + this._executionContext = executionContext; + } + + public get model(): ScvdComponentViewer { + return this._model; + } + + public get statementTree(): StatementBase | undefined { + return this._statementTree; + } + + public get executionContext(): ExecutionContext { + return this._executionContext; + } + + private buildStatement(item: ScvdNode, parent: StatementBase | undefined) : StatementBase | undefined { + if (item instanceof ScvdObject) { + // Object-specific logic + return new StatementObject(item, parent); + } + if (item instanceof ScvdVar) { + // Variable-specific logic. + return new StatementVar(item, parent); + } + if (item instanceof ScvdCalc) { + // Calculation-specific logic. + return new StatementCalc(item, parent); + } + if (item instanceof ScvdReadList) { + // ReadList-specific logic. + return new StatementReadList(item, parent); + } + if (item instanceof ScvdRead) { + // Read-specific logic. + return new StatementRead(item, parent); + } + if (item instanceof ScvdListOut) { + // ListOut-specific logic. + return new StatementListOut(item, parent); + } + if (item instanceof ScvdList) { + // List-specific logic. + return new StatementList(item, parent); + } + if (item instanceof ScvdOut) { + // Output-specific logic. + return new StatementOut(item, parent); + } + if (item instanceof ScvdItem) { + // Item-specific logic. + return new StatementItem(item, parent); + } + if (item instanceof ScvdPrint) { + // Print-specific logic. + return new StatementPrint(item, parent); + } + if (item instanceof ScvdBreak) { + // Break-specific logic. + return new StatementBreak(item, parent); + } + // Generic logic for other item types. + return undefined; + } + + public addChildrenFromScvd(item: ScvdNode, parent: StatementBase | undefined): StatementBase | undefined { + + const statement = this.buildStatement(item, parent); + if (statement === undefined) { + return undefined; + } + + for (const child of item.children) { + this.addChildrenFromScvd(child, statement); + } + + return statement; + } + + + public initialize(): boolean { + const objects = this._model.objects; + if (objects === undefined || objects.objects.length === 0) { + return false; + } + + const object = objects.objects[0]; + if (object === undefined) { + return false; + } + + const statementTree = this.addChildrenFromScvd(object, undefined); + if (statementTree !== undefined) { + statementTree.sortChildren(); + const breaks = this._model.breaks?.breaks ?? []; + for (const breakItem of breaks) { + this.insertBreakAtLine(statementTree, breakItem); + } + statementTree.sortChildren(); + this._statementTree = statementTree; + } + + return true; + } + + public async executeAll(guiTree: ScvdGuiTree): Promise { + // Execute all statements in the statement tree. + // This is a placeholder implementation. + + this._executionContext.memoryHost.clear(); + + if (this._statementTree) { + //console.log('Executing statements in the statement tree...'); + await this._statementTree.executeStatement(this.executionContext, guiTree); + } + } + + private insertBreakAtLine(root: StatementBase, breakItem: ScvdBreak): void { + const lineNo = Number(breakItem.getLineNoStr()); + if (this.hasBreakAtLine(root, lineNo)) { + return; + } + + const targetParent = this.findInsertionParentBySpan(root, lineNo); + this.buildStatement(breakItem, targetParent); + } + + private hasBreakAtLine(node: StatementBase, line: number): boolean { + const isBreak = node.scvdItem.constructor?.name === 'ScvdBreak' && node.line === line; + if (isBreak) { + return true; + } + for (const child of node.children) { + if (this.hasBreakAtLine(child, line)) { + return true; + } + } + return false; + } + + private findInsertionParentBySpan(node: StatementBase, targetLine: number): StatementBase { + for (const child of node.children) { + const min = child.line; + const max = this.getMaxLine(child); + if (targetLine >= min && targetLine <= max) { + return this.findInsertionParentBySpan(child, targetLine); + } + } + return node; + } + + private getMaxLine(node: StatementBase): number { + let max = node.line; + for (const child of node.children) { + const childMax = this.getMaxLine(child); + if (childMax > max) { + max = childMax; + } + } + return max; + } +} diff --git a/src/views/component-viewer/statement-engine/statement-item.ts b/src/views/component-viewer/statement-engine/statement-item.ts new file mode 100644 index 00000000..128256e4 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-item.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementItem extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + // TOIMPL: add printChildren to guiTree, and take the furst to set name/value for the item parent + public override async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + + const guiName = await this.scvdItem.getGuiName(); + const childGuiTree = this.getOrCreateGuiChild(guiTree, guiName, this.scvdItem.nodeId); + const guiValue = await this.scvdItem.getGuiValue(); + childGuiTree.setGuiName(guiName); + childGuiTree.setGuiValue(guiValue); + await this.onExecute(executionContext, childGuiTree); + + if (this.children.length > 0) { + for (const child of this.children) { + await child.executeStatement(executionContext, childGuiTree); + } + } + + if (guiName === undefined) { + const guiChildren = [...childGuiTree.children]; // copy to keep iteration safe during detach + for (const guiChild of guiChildren) { + if (guiChild.isPrint) { + const guiNamePrint = guiChild.getGuiName(); + const guiValuePrint = guiChild.getGuiValue(); + childGuiTree.setGuiName(guiNamePrint); + childGuiTree.setGuiValue(guiValuePrint); + break; // use first found + } + } + + for (const guiChild of guiChildren) { + if (guiChild.isPrint) { + guiChild.detach(); // remove temporary print nodes + } + } + + if (guiName === undefined && childGuiTree.children.length === 0) { // TOIMPL: check other conditions to drop + childGuiTree.detach(); // drop empty items that never produced a GUI name/value + } + } + } + + protected override async onExecute(_executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + //console.log(`${this.line}: Executing item: ${await this.scvdItem.getGuiName()}`); + } +} diff --git a/src/views/component-viewer/statement-engine/statement-list-out.ts b/src/views/component-viewer/statement-engine/statement-list-out.ts new file mode 100644 index 00000000..9c597b86 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-list-out.ts @@ -0,0 +1,143 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdListOut } from '../model/scvd-list-out'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementListOut extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + public override async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + + await this.onExecute(executionContext, guiTree); + /* Example code for evaluating children. + Normally this happens here, but in this case it’s done in onExecute + to account for the loop and its variables. + + for (const child of this.children) { // executed in list + await child.executeStatement(executionContext, guiTree); + }*/ + } + + protected override async onExecute(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const scvdList = this.scvdItem.castToDerived(ScvdListOut); + if (scvdList === undefined) { + console.error(`${this.line}: Executing "out-list": could not cast to ScvdList`); + return; + } + //console.log(`${this.line}: Executing out-list: ${scvdList.name}`); + + const name = scvdList.name; + if (name === undefined) { + console.error(`${this.line}: Executing "out-list": no name defined`); + return; + } + + const startExpr = scvdList.start; + if (startExpr === undefined) { + console.error(`${this.line}: Executing "out-list": ${scvdList.name}, no start expression defined`); + return; + } + const startValue = await startExpr.getValue(); + if (startValue === undefined) { + console.error(`${this.line}: Executing "out-list": ${scvdList.name}, could not evaluate start expression`); + return; + } + + const modelBase = executionContext.evalContext.container.base; + if (modelBase === undefined) { + console.error(`${this.line}: Executing "out-list": ${scvdList.name}, no base container defined`); + return; + } + + const varItem = modelBase.getSymbol(name); + if (varItem === undefined) { + console.error(`${this.line}: Executing "out-list": ${scvdList.name}, could not find variable in base container: ${modelBase.name}`); + return; + } + const varTargetSize = varItem.getTargetSize(); + if (varTargetSize === undefined) { + console.error(`${this.line}: Executing "out-list": ${scvdList.name}, variable: ${varItem.name}, could not determine target size`); + return; + } + + let limitValue = 0; + const limitExpr = scvdList.limit; + if (limitExpr !== undefined) { + const limitVal = await limitExpr.getValue(); + limitValue = limitVal !== undefined ? Number(limitVal) : 0; // do not enter loop if undefined + } + + const whileExpr = scvdList.while; + if (whileExpr !== undefined && limitExpr !== undefined) { + console.error(`${this.line}: Executing "out-list": ${scvdList.name}, cannot define both limit and while expressions`); + return; + } + + let loopValue = Number(startValue); + let maximumCount = 100000; // prevent infinite loops + while (maximumCount-- > 0) { + executionContext.memoryHost.setVariable(name, varTargetSize, loopValue, 0, undefined, varTargetSize); // update loop variable in memory + + /* while: Specifies the next value for iterations. + When using attribute while, iteration does not start if start==0. + */ + if (whileExpr !== undefined) { + if (loopValue === 0) { + break; + } + const whileValue = await whileExpr.getValue(); + const whileNum = whileValue !== undefined ? Number(whileValue) : undefined; + if (whileNum === 0 || whileNum === undefined) { // break on read error too + break; + } + } + if (limitExpr !== undefined) { + if (loopValue >= limitValue) { + break; + } + } + + for (const child of this.children) { // executed in list + await child.executeStatement(executionContext, guiTree); + } + + if (whileExpr !== undefined) { + const whileValue = await whileExpr.getValue(); + if (whileValue !== undefined) { + loopValue = Number(whileValue); + } + } + if (limitExpr !== undefined) { + loopValue++; + } + } + executionContext.memoryHost.setVariable(name, varTargetSize, loopValue, 0, undefined, varTargetSize); // update last loop variable in memory + return; + } +} diff --git a/src/views/component-viewer/statement-engine/statement-list.ts b/src/views/component-viewer/statement-engine/statement-list.ts new file mode 100644 index 00000000..5e4993dc --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-list.ts @@ -0,0 +1,131 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdList } from '../model/scvd-list'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementList extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + public async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + + await this.onExecute(executionContext, guiTree); + /* Example code for evaluating children. + Normally this happens here, but in this case it’s done in onExecute + to account for the loop and its variables. + + for (const child of this.children) { // executed in list + await child.executeStatement(executionContext, guiTree); + }*/ + } + + protected async onExecute(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const scvdList = this.scvdItem.castToDerived(ScvdList); + if (scvdList === undefined) { + console.error(`${this.line}: Executing "list": could not cast to ScvdList`); + return; + } + //console.log(`${this.line}: Executing list: ${scvdList.name}`); + + const name = scvdList.name; + if (name === undefined) { + console.error(`${this.line}: Executing "list": no name defined`); + return; + } + + const startExpr = scvdList.start; + if (startExpr === undefined) { + console.error(`${this.line}: Executing "list": ${scvdList.name}, no start expression defined`); + return; + } + const startValue = await startExpr.getValue(); + if (startValue === undefined) { + console.error(`${this.line}: Executing "list": ${scvdList.name}, could not evaluate start expression`); + return; + } + + const modelBase = executionContext.evalContext.container.base; + if (modelBase === undefined) { + console.error(`${this.line}: Executing "list": ${scvdList.name}, no base container defined`); + return; + } + + const varItem = modelBase.getSymbol(name); + if (varItem === undefined) { + console.error(`${this.line}: Executing "list": ${scvdList.name}, could not find variable in base container: ${modelBase.name}`); + return; + } + const varTargetSize = varItem.getTargetSize(); + if (varTargetSize === undefined) { + console.error(`${this.line}: Executing "list": ${scvdList.name}, variable: ${varItem.name}, could not determine target size`); + return; + } + + let limitValue = 0; + const limitExpr = scvdList.limit; + if (limitExpr !== undefined) { + const limitVal = await limitExpr.getValue(); + limitValue = limitVal !== undefined ? Number(limitVal) : 0; // do not enter loop if undefined + } + + const whileExpr = scvdList.while; + if (whileExpr !== undefined && limitExpr !== undefined) { + console.error(`${this.line}: Executing "list": ${scvdList.name}, cannot define both limit and while expressions`); + return; + } + + let loopValue = Number(startValue); + let maximumCount = 100000; // prevent infinite loops + while (maximumCount-- > 0) { + executionContext.memoryHost.setVariable(name, varTargetSize, loopValue, 0, undefined, varTargetSize); // update loop variable in memory + + // while-loop + if (whileExpr !== undefined) { + const whileValue = await whileExpr.getValue(); + if (whileValue !== undefined) { + loopValue = Number(whileValue); + } + if (loopValue === 0 || whileValue === undefined) { // break on read error too + break; + } + } + // for-loop: test condition, exit if met + if (limitExpr !== undefined) { + if (loopValue >= limitValue) { + break; + } + loopValue++; + } + + for (const child of this.children) { // executed in list + await child.executeStatement(executionContext, guiTree); + } + } + executionContext.memoryHost.setVariable(name, varTargetSize, loopValue, 0, undefined, varTargetSize); // update last loop variable in memory + } +} diff --git a/src/views/component-viewer/statement-engine/statement-object.ts b/src/views/component-viewer/statement-engine/statement-object.ts new file mode 100644 index 00000000..b08d351a --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-object.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { StatementBase } from './statement-base'; + + +export class StatementObject extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + +} diff --git a/src/views/component-viewer/statement-engine/statement-out.ts b/src/views/component-viewer/statement-engine/statement-out.ts new file mode 100644 index 00000000..ff83d673 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-out.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementOut extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + public override async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + + const guiName = await this.scvdItem.getGuiName(); + const childGuiTree = this.getOrCreateGuiChild(guiTree, guiName, this.scvdItem.nodeId); + await this.onExecute(executionContext, childGuiTree); + + if (this.children.length > 0) { + for (const child of this.children) { + await child.executeStatement(executionContext, childGuiTree); + } + } + } + + protected override async onExecute(_executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + //console.log(`${this.line}: Executing out: ${await this.scvdItem.getGuiName()}`); + + const guiName = await this.scvdItem.getGuiName(); + guiTree.setGuiName(guiName); + } +} diff --git a/src/views/component-viewer/statement-engine/statement-print.ts b/src/views/component-viewer/statement-engine/statement-print.ts new file mode 100755 index 00000000..9de0a2e2 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-print.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementPrint extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + public override async executeStatement(executionContext: ExecutionContext, guiTree: ScvdGuiTree): Promise { + const conditionResult = await this.scvdItem.getConditionResult(); + if (conditionResult === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping ${this.scvdItem.getDisplayLabel()} for condition result: ${conditionResult}`); + return; + } + + const guiName = await this.scvdItem.getGuiName(); + const guiValue = await this.scvdItem.getGuiValue(); + const childGuiTree = this.getOrCreateGuiChild(guiTree, guiName, this.scvdItem.nodeId); + childGuiTree.setGuiName(guiName); + childGuiTree.setGuiValue(guiValue); + childGuiTree.isPrint = true; + + await this.onExecute(executionContext, childGuiTree); + + if (this.children.length > 0) { + for (const child of this.children) { + await child.executeStatement(executionContext, childGuiTree); + } + } + } + + protected override async onExecute(_executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + //console.log(`${this.line}: Executing print: ${await this.scvdItem.getGuiName()}`); + } +} diff --git a/src/views/component-viewer/statement-engine/statement-read.ts b/src/views/component-viewer/statement-engine/statement-read.ts new file mode 100644 index 00000000..67b11657 --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-read.ts @@ -0,0 +1,111 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdRead } from '../model/scvd-read'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementRead extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + protected override async onExecute(executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + //console.log(`${this.line}: Executing read: ${this.scvdItem.getDisplayLabel()}`); + const mustRead = this.scvdItem.mustRead; + if (mustRead === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping "read" as already initialized: ${this.scvdItem.name}`); + return; + } + + const scvdRead = this.scvdItem.castToDerived(ScvdRead); + if (scvdRead === undefined) { + return; + } + + const name = scvdRead.name; + if (name === undefined) { + console.error(`${this.line}: Executing "read": no name defined`); + return; + } + + const targetSize = scvdRead.getTargetSize(); // use size specified in SCVD + if (targetSize === undefined) { + console.error(`${this.line} Executing "read": ${scvdRead.name}, type: ${scvdRead.getDisplayLabel()}, could not determine target size`); + return; + } + const virtualSize = scvdRead.getVirtualSize() ?? targetSize; + const sizeValue = await scvdRead.getArraySize(); + const numOfElements = sizeValue ?? 1; + const readBytes = numOfElements * targetSize; // Is an Expressions representing the array size or the number of values to read from target. The maximum array size is limited to 512. Default value is 1. + const fullVirtualStrideSize = virtualSize * numOfElements; + let baseAddress: number | bigint | undefined = undefined; + + // Check if symbol address is defined + const symbol = scvdRead.symbol; + if (symbol?.symbol !== undefined) { + const symAddr = await executionContext.debugTarget.findSymbolAddress(symbol.symbol); + if (symAddr === undefined) { + console.error(`${this.line}: Executing "read": ${scvdRead.name}, symbol: ${symbol?.name}, could not find symbol address for symbol: ${symbol?.symbol}`); + return; + } + baseAddress = typeof symAddr === 'bigint' ? symAddr : (symAddr >>> 0); + } + + const offset = scvdRead.offset ? await scvdRead.offset.getValue() : undefined; + if (offset !== undefined) { + let offs: bigint | undefined; + if (typeof offset === 'bigint') { + offs = offset; + } else if (typeof offset === 'number') { + offs = BigInt(Math.trunc(offset)); + } else { + console.error(`${this.line}: Executing "read": ${scvdRead.name}, offset is not numeric`); + return; + } + if (offs !== undefined) { + baseAddress = baseAddress !== undefined + ? (typeof baseAddress === 'bigint' ? baseAddress + offs : (BigInt(baseAddress >>> 0) + offs)) + : offs; + } + } + + if (baseAddress === undefined) { + console.error(`${this.line}: Executing "read": ${scvdRead.name}, symbol: ${symbol?.name}, could not find symbol address for symbol: ${symbol?.symbol}`); + return; + } + //console.log(`${this.line}: Executing target read: ${scvdRead.name}, symbol: ${symbol?.name}, address: ${baseAddress}, size: ${readBytes} bytes`); + + // Read from target memory + const readData = await executionContext.debugTarget.readMemory(baseAddress, readBytes); + if (readData === undefined) { + console.error(`${this.line}: Executing "read": ${scvdRead.name}, symbol: ${symbol?.name}, address: ${baseAddress}, size: ${readBytes} bytes, read target memory failed`); + return; + } + + // Write to local variable cache + executionContext.memoryHost.setVariable(name, readBytes, readData, 0, typeof baseAddress === 'bigint' ? Number(baseAddress) : (baseAddress >>> 0), fullVirtualStrideSize); + + if (scvdRead.const === true) { // Mark variable as already initialized + scvdRead.mustRead = false; + } + return; + } +} diff --git a/src/views/component-viewer/statement-engine/statement-readList.ts b/src/views/component-viewer/statement-engine/statement-readList.ts new file mode 100644 index 00000000..b0cc0c6b --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-readList.ts @@ -0,0 +1,208 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdReadList } from '../model/scvd-readlist'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementReadList extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + protected override async onExecute(executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + const mustRead = this.scvdItem.mustRead; + if (mustRead === false) { + //console.log(`${this.scvdItem.getLineNoStr()}: Skipping "read" as already initialized: ${this.scvdItem.name}`); + return; + } + + const scvdReadList = this.scvdItem.castToDerived(ScvdReadList); + if (scvdReadList === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": could not cast to ScvdReadList`); + return; + } + + // ---- fetch item name ---- + const itemName = scvdReadList.name; + if (itemName === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": no name defined`); + return; + } + + // ---- handle init ---- + const init = scvdReadList.getInit(); // When init="1" previous read items in the list are discarded. Default value is 0. + if (init === 1) { + executionContext.memoryHost.clearVariable(itemName); + } + + // ---- fetch type size ---- + const targetSize = scvdReadList.getTargetSize(); + if (targetSize === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, type: ${scvdReadList.getDisplayLabel()}, could not determine target size`); + return; + } + const virtualSize = scvdReadList.getVirtualSize() ?? targetSize; // if type has members, include their size in the variable allocation + + // ---- calculate read size ---- + const isPointer = scvdReadList.getIsPointer(); // When based="1" the attribute symbol and attribute offset specifies a pointer (or pointer array). Default value is 0. + const readBytes = (isPointer === true)? 4 : targetSize; + const virtualBytes = (isPointer === true)? 4 : virtualSize; + + // ---- calculate base address from symbol and/or offset ---- + let baseAddress: number | bigint | undefined = undefined; + let maxArraySize: number = 1024; + + // Check if symbol address is defined, use as base address + const symbol = scvdReadList.symbol; + if (symbol?.symbol !== undefined) { + const symAddr = await executionContext.debugTarget.findSymbolAddress(symbol.symbol); + if (symAddr === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, could not find symbol address for symbol: ${symbol?.symbol}`); + return; + } + baseAddress = typeof symAddr === 'bigint' ? symAddr : (symAddr >>> 0); + + // fetch maximum existing array size + const resolvedCount = await executionContext.debugTarget.getNumArrayElements(symbol.symbol); + maxArraySize = resolvedCount ?? 1; + } + + // Add offset to base address. If no symbol defined, offset is used as base address + const offset = scvdReadList.offset ? await scvdReadList.offset.getValue() : undefined; // Offset is attr: size plus var symbols! + if (offset !== undefined) { + let offs: bigint | undefined; + if (typeof offset === 'bigint') { + offs = offset; + } else if (typeof offset === 'number') { + offs = BigInt(Math.trunc(offset)); + } else { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, offset is not numeric`); + return; + } + baseAddress = baseAddress !== undefined + ? (typeof baseAddress === 'bigint' ? baseAddress + offs : (BigInt(baseAddress >>> 0) + offs)) + : offs; + } + + // Check that base address is valid + if (baseAddress === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, could not find symbol address for symbol: ${symbol?.symbol}`); + return; + } + + // ---- prepare for linked list read if next member is defined ---- + let nextOffset: number | undefined = undefined; + let nextTargetSize: number | undefined = undefined; + + const next = scvdReadList.getNext(); // Name of a member element in the list that is used as next pointer. This is used to read a linked list. stops reading on a NULL pointer. + if (next !== undefined) { + // ---- fetch type info ---- + const typeItem = scvdReadList.type; + if (typeItem === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, no type defined`); + return; + } + + const nextMember = typeItem.getMember(next); + if (nextMember !== undefined) { + nextTargetSize = nextMember.getTargetSize(); + nextOffset = await nextMember.getMemberOffset(); + if (nextTargetSize === undefined || nextOffset === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, could not determine size/offset of next member: ${next} in type: ${typeItem.getDisplayLabel()}`); + return; + } + if (nextTargetSize > 4) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, next member: ${next} size is larger than 4 bytes (${nextTargetSize} bytes)`); + return; + } + } + } + //console.log(`${this.scvdItem.getLineNoStr()}: Executing target readlist: ${scvdReadList.name}, symbol: ${symbol?.name}, address: ${baseAddress}, size: ${readBytes} bytes`); + + // ---- fetch count of items to read. count is always 1..1024 ---- + const count = await scvdReadList.getCount(); // Number of list items to read, default is 1. Limited to 1..1024 in ScvdExpression. + + // ---- calculate next address ---- + let nextPtrAddr: number | bigint | undefined = typeof baseAddress === 'bigint' ? baseAddress : (baseAddress >>> 0); + + let readIdx = 0; + while (nextPtrAddr !== undefined) { + const itemAddress: number | bigint | undefined = typeof nextPtrAddr === 'bigint' ? nextPtrAddr : (nextPtrAddr >>> 0); + + // Read data from target + const readData = await executionContext.debugTarget.readMemory(itemAddress, readBytes); + if (readData === undefined) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, address: ${baseAddress}, size: ${readBytes} bytes, read target memory failed`); + break; + } + + // Store in memory host + executionContext.memoryHost.setVariable(itemName, readBytes, readData, -1, typeof itemAddress === 'bigint' ? Number(itemAddress) : itemAddress, virtualBytes); + readIdx ++; + + // check count + if (count !== undefined) { + if (readIdx >= count) { + break; + } else if (readIdx > maxArraySize) { + console.warn(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, reached maximum array size: ${maxArraySize} for variable: ${itemName}`); + break; + } + } + // Check overall maximum read size + if (readIdx >= ScvdReadList.READ_SIZE_MAX) { + break; + } + // If neither count or next is defined, read only one item + if (count === undefined && next === undefined) { + break; + } + + // calculate next address + if (next) { + if (nextTargetSize === undefined || nextOffset === undefined) { + break; + } + const nextPtrUint8Arr = readData.subarray(nextOffset, nextOffset + nextTargetSize); + if (nextPtrUint8Arr.length !== nextTargetSize) { + console.error(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, could not extract next pointer data from read data`); + break; + } + nextPtrAddr = (nextPtrUint8Arr[0] | (nextPtrUint8Arr[1] << 8) | (nextPtrUint8Arr[2] << 16) | (nextPtrUint8Arr[3] << 24)) >>> 0; + } else { + const baseNum = typeof baseAddress === 'bigint' ? baseAddress : BigInt(baseAddress >>> 0); + const stride = BigInt(isPointer ? (readIdx * 4) : (readIdx * targetSize)); + nextPtrAddr = baseNum + stride; + } + + if (nextPtrAddr === 0 || nextPtrAddr === 0n) { // NULL pointer, end of linked list + nextPtrAddr = undefined; + } else if (nextPtrAddr === itemAddress) { // loop detection + console.warn(`${this.scvdItem.getLineNoStr()}: Executing "readlist": ${scvdReadList.name}, symbol: ${symbol?.name}, detected loop in linked list at address: ${itemAddress.toString(16)}`); + break; + } + } + + if (scvdReadList.const === true) { // Mark variable as already initialized + scvdReadList.mustRead = false; + } + } +} diff --git a/src/views/component-viewer/statement-engine/statement-var.ts b/src/views/component-viewer/statement-engine/statement-var.ts new file mode 100644 index 00000000..f298b78a --- /dev/null +++ b/src/views/component-viewer/statement-engine/statement-var.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ScvdNode } from '../model/scvd-node'; +import { ScvdVar } from '../model/scvd-var'; +import { ExecutionContext } from '../scvd-eval-context'; +import { ScvdGuiTree } from '../scvd-gui-tree'; +import { StatementBase } from './statement-base'; + + +export class StatementVar extends StatementBase { + + constructor(item: ScvdNode, parent: StatementBase | undefined) { + super(item, parent); + } + + protected override async onExecute(executionContext: ExecutionContext, _guiTree: ScvdGuiTree): Promise { + //console.log(`${this.line}: Executing var: ${await this.scvdItem.getGuiName()}`); + + const varItem = this.scvdItem.castToDerived(ScvdVar); + if (varItem !== undefined) { + const name = varItem.name; + const targetSize = varItem.getTargetSize(); + const value = await varItem.getValue(); + if (name !== undefined && targetSize !== undefined && value !== undefined) { + executionContext.memoryHost.setVariable(name, targetSize, value, -1, 0); + //console.log(`${this.line} Variable "${name}" created with value: ${value}`); + } + } + } +} diff --git a/src/views/component-viewer/test/integration/component-viewer-logger.test.ts b/src/views/component-viewer/test/integration/component-viewer-logger.test.ts new file mode 100644 index 00000000..8884fd92 --- /dev/null +++ b/src/views/component-viewer/test/integration/component-viewer-logger.test.ts @@ -0,0 +1,47 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ComponentViewerLogger. + */ + +jest.mock('vscode', () => { + const mockChannel = { appendLine: jest.fn(), dispose: jest.fn() }; + const createOutputChannel = jest.fn(() => mockChannel); + return { + window: { createOutputChannel }, + __mock: { createOutputChannel, mockChannel }, + }; +}); + +jest.mock('../../../../manifest', () => ({ + COMPONENT_VIEWER_DISPLAY_NAME: 'Component Viewer', +})); + +describe('component-viewer-logger', () => { + beforeEach(() => { + jest.resetModules(); + }); + + it('creates a logger output channel with log option', async () => { + const { __mock } = jest.requireMock('vscode') as { __mock: { createOutputChannel: jest.Mock; mockChannel: unknown } }; + const { componentViewerLogger } = await import('../../component-viewer-logger'); + expect(__mock.createOutputChannel).toHaveBeenCalledWith('Component Viewer', { log: true }); + expect(componentViewerLogger).toBe(__mock.mockChannel); + }); +}); diff --git a/src/views/component-viewer/test/integration/component-viewer-target-access.test.ts b/src/views/component-viewer/test/integration/component-viewer-target-access.test.ts new file mode 100644 index 00000000..38a3a2fd --- /dev/null +++ b/src/views/component-viewer/test/integration/component-viewer-target-access.test.ts @@ -0,0 +1,218 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ComponentViewerTargetAccess. + */ + +import * as vscode from 'vscode'; +import { ComponentViewerTargetAccess } from '../../component-viewer-target-access'; +import { debugSessionFactory } from '../../../../__test__/vscode.factory'; +import { GDBTargetDebugSession } from '../../../../debug-session'; +import { logger } from '../../../../logger'; + +describe('ComponentViewerTargetAccess', () => { + const defaultConfig = () => { + return { + name: 'test-session', + type: 'gdbtarget', + request: 'launch' + }; + }; + + let debugSession: vscode.DebugSession; + let gdbTargetSession: GDBTargetDebugSession; + let targetAccess: ComponentViewerTargetAccess; + + beforeEach(() => { + debugSession = debugSessionFactory(defaultConfig()); + gdbTargetSession = new GDBTargetDebugSession(debugSession); + targetAccess = new ComponentViewerTargetAccess(); + targetAccess.setActiveSession(gdbTargetSession); + }); + + describe('evaluateSymbolAddress', () => { + it('should evaluate symbol address successfully with default context', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ + result: '0x20000000', + variablesReference: 0 + }); + (vscode.debug.activeStackItem as unknown) = { session: debugSession, threadId: 1, frameId: 5 }; + + const result = await targetAccess.evaluateSymbolAddress('myVariable'); + + expect(result).toBe('0x20000000'); + expect(debugSession.customRequest).toHaveBeenCalledWith('evaluate', { + expression: '&myVariable', + frameId: 5, + context: 'hover' + }); + + // Cleanup + (vscode.debug.activeStackItem as unknown) = undefined; + }); + + it('should evaluate symbol address with custom context', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ + result: '0x40001000', + variablesReference: 0 + }); + (vscode.debug.activeStackItem as unknown) = { session: debugSession, threadId: 1, frameId: 3 }; + + const result = await targetAccess.evaluateSymbolAddress('myStruct', 'watch'); + + expect(result).toBe('0x40001000'); + expect(debugSession.customRequest).toHaveBeenCalledWith('evaluate', { + expression: '&myStruct', + frameId: 3, + context: 'watch' + }); + + // Cleanup + (vscode.debug.activeStackItem as unknown) = undefined; + }); + + it('should use frameId 0 when no active stack frame exists', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ + result: '0x30000000', + variablesReference: 0 + }); + (vscode.debug.activeStackItem as unknown) = undefined; + + const result = await targetAccess.evaluateSymbolAddress('globalVar'); + + expect(result).toBe('0x30000000'); + expect(debugSession.customRequest).toHaveBeenCalledWith('evaluate', { + expression: '&globalVar', + frameId: 0, + context: 'hover' + }); + }); + + it('should return error message when evaluation fails', async () => { + const logDebugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('Variable not found')); + + const result = await targetAccess.evaluateSymbolAddress('unknownVar'); + + expect(result).toBeUndefined(); + expect(logDebugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate address \'unknownVar\' - \'Variable not found\'' + ); + }); + + it('should return "No active session" when custom request fails', async () => { + const logDebugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('custom request failed')); + + const result = await targetAccess.evaluateSymbolAddress('myVar'); + + expect(result).toBeUndefined(); + expect(logDebugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate address \'myVar\' - \'custom request failed\'' + ); + }); + }); + + describe('evaluateMemory', () => { + it('should read memory successfully', async () => { + const memoryData = 'AQIDBAU='; // Base64 encoded data + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ + address: '0x20000000', + data: memoryData + }); + + const result = await targetAccess.evaluateMemory('0x20000000', 16, 0); + + expect(result).toBe(memoryData); + expect(debugSession.customRequest).toHaveBeenCalledWith('readMemory', { + memoryReference: '0x20000000', + count: 16, + offset: 0 + }); + }); + + it('should read memory with offset', async () => { + const memoryData = 'AQIDBAU='; + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ + address: '0x20000004', + data: memoryData + }); + + const result = await targetAccess.evaluateMemory('0x20000000', 8, 4); + + expect(result).toBe(memoryData); + expect(debugSession.customRequest).toHaveBeenCalledWith('readMemory', { + memoryReference: '0x20000000', + count: 8, + offset: 4 + }); + }); + + it('should return undefined when memory read fails', async () => { + const logDebugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('Invalid memory address')); + + const result = await targetAccess.evaluateMemory('0xFFFFFFFF', 4, 0); + + expect(result).toBeUndefined(); + expect(logDebugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to read memory at address \'0xFFFFFFFF\' - \'Invalid memory address\'' + ); + }); + + it('should return undefined when custom request fails for memory read', async () => { + const logDebugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('custom request failed')); + + const result = await targetAccess.evaluateMemory('0x20000000', 4, 0); + + expect(result).toBeUndefined(); + expect(logDebugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to read memory at address \'0x20000000\' - \'custom request failed\'' + ); + }); + + it('should handle undefined response data', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ + address: '0x20000000' + // data is undefined + }); + + const result = await targetAccess.evaluateMemory('0x20000000', 4, 0); + + expect(result).toBeUndefined(); + }); + }); + + describe('constructor', () => { + it('should initialize with active session', () => { + expect(targetAccess).toBeDefined(); + expect(targetAccess['_activeSession']).toBe(gdbTargetSession); + }); + + it('should create instance with different session', () => { + const anotherDebugSession = debugSessionFactory({ ...defaultConfig(), name: 'another-session' }); + const anotherGDBSession = new GDBTargetDebugSession(anotherDebugSession); + const anotherTargetAccess = new ComponentViewerTargetAccess(); + anotherTargetAccess.setActiveSession(anotherGDBSession); + + expect(anotherTargetAccess['_activeSession']).toBe(anotherGDBSession); + }); + }); +}); diff --git a/src/views/component-viewer/test/integration/helpers/full-data-host.ts b/src/views/component-viewer/test/integration/helpers/full-data-host.ts new file mode 100644 index 00000000..59ad7dde --- /dev/null +++ b/src/views/component-viewer/test/integration/helpers/full-data-host.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Test-only helper for evaluator host typing. + * Integration test for FullDataHost. + */ + +import type { DataAccessHost, ModelHost } from '../../../parser-evaluator/model-host'; +import type { IntrinsicProvider } from '../../../parser-evaluator/intrinsics'; + +export type FullDataHost = ModelHost & DataAccessHost & IntrinsicProvider; diff --git a/src/views/component-viewer/test/integration/helpers/statement-engine-helpers.test.ts b/src/views/component-viewer/test/integration/helpers/statement-engine-helpers.test.ts new file mode 100644 index 00000000..3e43e5ce --- /dev/null +++ b/src/views/component-viewer/test/integration/helpers/statement-engine-helpers.test.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for StatementEngineHelpers. + */ + +import { MemoryHost } from '../../../data-host/memory-host'; +import { RegisterHost } from '../../../data-host/register-host'; +import { TestNode, createExecutionContext } from '../../unit/helpers/statement-engine-helpers'; + +describe('statement-engine helpers', () => { + it('exposes TestNode defaults and setters', async () => { + const node = new TestNode(undefined); + + await expect(node.getConditionResult()).resolves.toBe(true); + await expect(node.getGuiName()).resolves.toBeUndefined(); + await expect(node.getGuiValue()).resolves.toBeUndefined(); + + node.conditionResult = false; + node.guiName = 'Title'; + node.guiValue = '42'; + + await expect(node.getConditionResult()).resolves.toBe(false); + await expect(node.getGuiName()).resolves.toBe('Title'); + await expect(node.getGuiValue()).resolves.toBe('42'); + }); + + it('creates execution contexts with defaults and overrides', async () => { + const base = new TestNode(undefined); + const findSymbolAddress = jest.fn(async () => 123); + const readMemory = jest.fn(async () => new Uint8Array([1, 2, 3])); + + const ctx = createExecutionContext(base, { findSymbolAddress, readMemory }); + + expect(ctx.memoryHost).toBeInstanceOf(MemoryHost); + expect(ctx.registerHost).toBeInstanceOf(RegisterHost); + expect(ctx.evalContext).toBeDefined(); + expect(ctx.evalContext.container.base).toBe(base); + + await expect(ctx.debugTarget.findSymbolAddress('sym')).resolves.toBe(123); + expect(findSymbolAddress).toHaveBeenCalledWith('sym'); + + await expect(ctx.debugTarget.readMemory(0, 3)).resolves.toEqual(new Uint8Array([1, 2, 3])); + expect(readMemory).toHaveBeenCalledWith(0, 3); + + await expect(ctx.debugTarget.getNumArrayElements('arr')).resolves.toBeUndefined(); + }); + + it('uses default debug target handlers when not overridden', async () => { + const base = new TestNode(undefined); + const ctx = createExecutionContext(base); + + await expect(ctx.debugTarget.findSymbolAddress('sym')).resolves.toBeUndefined(); + await expect(ctx.debugTarget.getNumArrayElements('arr')).resolves.toBeUndefined(); + await expect(ctx.debugTarget.readMemory(0, 4)).resolves.toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/integration/memory-host/memory-host.test.ts b/src/views/component-viewer/test/integration/memory-host/memory-host.test.ts new file mode 100644 index 00000000..934928cc --- /dev/null +++ b/src/views/component-viewer/test/integration/memory-host/memory-host.test.ts @@ -0,0 +1,197 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for MemoryHost. + */ + +import { MemoryHost } from '../../../data-host/memory-host'; +import { RefContainer } from '../../../parser-evaluator/model-host'; +import { ScvdNode } from '../../../model/scvd-node'; + +class NamedStubBase extends ScvdNode { + constructor(name: string) { + super(undefined); + this.name = name; + } +} + +const makeContainer = (name: string, widthBytes: number, offsetBytes = 0): RefContainer => { + const ref = new NamedStubBase(name); + return { + base: ref, + anchor: ref, + current: ref, + offsetBytes, + widthBytes, + valueType: undefined, + }; +}; + +describe('MemoryHost', () => { + it('stores and retrieves numeric values with explicit offsets', () => { + const host = new MemoryHost(); + + host.setVariable('foo', 4, 0x12345678, 0); + expect(host.getVariable('foo')).toBe(0x12345678); + + host.setVariable('foo', 2, 0xabcd, 4); + expect(host.getVariable('foo', 2, 4)).toBe(0xabcd); + }); + + it('appends when offset is -1 and tracks element count', () => { + const host = new MemoryHost(); + host.setVariable('arr', 4, 1, -1); + host.setVariable('arr', 4, 2, -1); + host.setVariable('arr', 4, 3, -1); + + expect(host.getArrayElementCount('arr')).toBe(3); + expect(host.getVariable('arr', 4, 0)).toBe(1); + expect(host.getVariable('arr', 4, 4)).toBe(2); + expect(host.getVariable('arr', 4, 8)).toBe(3); + }); + + it('rejects spans larger than 4 bytes via getVariable', () => { + const host = new MemoryHost(); + host.setVariable('big', 8, new Uint8Array(8), 0); + expect(host.getVariable('big', 8, 0)).toBeUndefined(); + }); + + it('tracks target bases and allows updating them', () => { + const host = new MemoryHost(); + host.setVariable('sym', 4, 1, -1, 0x1000); + host.setVariable('sym', 4, 2, -1, 0x2000); + + expect(host.getElementTargetBase('sym', 0)).toBe(0x1000); + expect(host.getElementTargetBase('sym', 1)).toBe(0x2000); + + host.setElementTargetBase('sym', 1, 0x3000); + expect(host.getElementTargetBase('sym', 1)).toBe(0x3000); + }); + + it('supports readValue/writeValue round-trips for numbers', async () => { + const host = new MemoryHost(); + const container = makeContainer('num', 4); + + await host.writeValue(container, 0xdeadbeef); + const out = await host.readValue(container); + expect(out).toBe(0xdeadbeef >>> 0); + }); + + it('supports readValue/writeValue for byte arrays', async () => { + const host = new MemoryHost(); + const bytes = new Uint8Array([1, 2, 3, 4, 5, 6]); + const container = makeContainer('blob', bytes.length); + + await host.writeValue(container, bytes); + const out = await host.readValue(container); + expect(out).toEqual(bytes); + }); + + it('writes and reads raw bytes at offsets', async () => { + const host = new MemoryHost(); + const container = makeContainer('raw', 4, 2); + + await host.writeValue(container, new Uint8Array([9, 8, 7, 6])); + const out = await host.readRaw(container, 4); + + expect(out).toEqual(new Uint8Array([9, 8, 7, 6])); + }); + + it('round-trips via setVariable/getVariable for a simple read', () => { + const host = new MemoryHost(); + host.setVariable('simple', 2, 0x1234, 0); + + expect(host.getVariable('simple')).toBe(0x1234); + }); + + it('preserves untouched bytes on partial overwrites', async () => { + const host = new MemoryHost(); + const base = makeContainer('overlap', 4, 0); + const tail = makeContainer('overlap', 2, 2); + + await host.writeValue(base, new Uint8Array([1, 2, 3, 4])); + await host.writeValue(tail, new Uint8Array([9, 8])); + + const out = await host.readRaw(base, 4); + expect(out).toEqual(new Uint8Array([1, 2, 9, 8])); + }); + + it('partial setVariable writes only affect the specified range', () => { + const host = new MemoryHost(); + + host.setVariable('window', 4, new Uint8Array([1, 2, 3, 4]), 0); + host.setVariable('window', 2, new Uint8Array([9, 8]), 2); + + expect(host.getVariable('window', 2, 0)).toBe(0x0201); + expect(host.getVariable('window', 2, 2)).toBe(0x0809); + }); + + it('zero-fills virtual size and supports writes into virtual space', async () => { + const host = new MemoryHost(); + + host.setVariable('struct', 2, new Uint8Array([0xAA, 0xBB]), 0, undefined, 6); + + const base = makeContainer('struct', 2, 0); + const mid = makeContainer('struct', 2, 2); + const tail = makeContainer('struct', 2, 4); + + expect(await host.readRaw(base, 2)).toEqual(new Uint8Array([0xAA, 0xBB])); + expect(await host.readRaw(mid, 2)).toEqual(new Uint8Array([0x00, 0x00])); + expect(await host.readRaw(tail, 2)).toEqual(new Uint8Array([0x00, 0x00])); + + await host.writeValue(mid, new Uint8Array([0x11, 0x22])); + + expect(await host.readRaw(base, 2)).toEqual(new Uint8Array([0xAA, 0xBB])); + expect(await host.readRaw(mid, 2)).toEqual(new Uint8Array([0x11, 0x22])); + expect(await host.readRaw(tail, 2)).toEqual(new Uint8Array([0x00, 0x00])); + + await host.writeValue(tail, new Uint8Array([0x33, 0x44])); + + expect(await host.readRaw(base, 2)).toEqual(new Uint8Array([0xAA, 0xBB])); + expect(await host.readRaw(mid, 2)).toEqual(new Uint8Array([0x11, 0x22])); + expect(await host.readRaw(tail, 2)).toEqual(new Uint8Array([0x33, 0x44])); + }); + + it('expands the backing buffer when writing beyond current size', async () => { + const host = new MemoryHost(); + const head = makeContainer('grow', 2, 0); + const tail = makeContainer('grow', 2, 6); + + await host.writeValue(head, new Uint8Array([1, 2])); + await host.writeValue(tail, new Uint8Array([9, 8])); + + expect(await host.readRaw(head, 2)).toEqual(new Uint8Array([1, 2])); + expect(await host.readRaw(tail, 2)).toEqual(new Uint8Array([9, 8])); + }); + + it('appends when offset is -1 and later writes can expand via the interface', async () => { + const host = new MemoryHost(); + + host.setVariable('arr', 2, new Uint8Array([1, 2]), -1); + host.setVariable('arr', 2, new Uint8Array([3, 4]), -1); + + const appended = makeContainer('arr', 2, 4); + await host.writeValue(appended, new Uint8Array([5, 6])); + + expect(host.getArrayElementCount('arr')).toBe(2); + expect(await host.readRaw(makeContainer('arr', 2, 0), 2)).toEqual(new Uint8Array([1, 2])); + expect(await host.readRaw(makeContainer('arr', 2, 2), 2)).toEqual(new Uint8Array([3, 4])); + expect(await host.readRaw(makeContainer('arr', 2, 4), 2)).toEqual(new Uint8Array([5, 6])); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.formatPrintf.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.formatPrintf.test.ts new file mode 100644 index 00000000..f731de1e --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.formatPrintf.test.ts @@ -0,0 +1,313 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit tests for ScvdEvalInterface.formatPrintf based on the CMSIS-View + * value_output specification: https://arm-software.github.io/CMSIS-View/main/value_output.html + * Integration test for ScvdEvalInterface.formatPrintf. + */ + +import { ScvdEvalInterface } from '../../../../scvd-eval-interface'; +import { ScvdFormatSpecifier, FormatKind } from '../../../../model/scvd-format-specifier'; +import { RefContainer } from '../../../../parser-evaluator/model-host'; +import { MemoryHost } from '../../../../data-host/memory-host'; +import { RegisterHost } from '../../../../data-host/register-host'; +import { ScvdNode } from '../../../../model/scvd-node'; +import { ScvdMember } from '../../../../model/scvd-member'; +import { ScvdDebugTarget } from '../../../../scvd-debug-target'; + +class FakeBase extends ScvdNode { + constructor(typeName?: string) { + super(undefined); + this._typeName = typeName; + } + + private _typeName: string | undefined; + + public override getValueType(): string | undefined { + return this._typeName; + } + + public override getIsPointer(): boolean { + return false; + } + + public override getTargetSize(): number | undefined { + return 4; + } + + public override getTypeSize(): number | undefined { + return 4; + } +} + +class FakeMember extends ScvdMember { + constructor() { + super(undefined); + } + + public override getTargetSize(): number | undefined { + return 4; + } + + public override async getEnum(_value: number) { + return { + getGuiName: async () => 'ENUM_READY' + } as unknown as Awaited>; + } +} + +class FakeDebugTarget implements Pick { + constructor( + private readonly symbolMap: Map, + private readonly memoryMap: Map + ) {} + + public async findSymbolContextAtAddress(_addr: number): Promise { + return undefined; + } + + public async findSymbolNameAtAddress(addr: number): Promise { + return this.symbolMap.get(addr); + } + + public async readUint8ArrayStrFromPointer(address: number, _bytesPerChar: number, maxLength: number): Promise { + const data = this.memoryMap.get(address); + if (!data) { + return undefined; + } + return data.subarray(0, Math.min(maxLength, data.length)); + } + + public async readMemory(address: number, size: number): Promise { + const data = this.memoryMap.get(address); + if (!data) { + return undefined; + } + return data.subarray(0, Math.min(size, data.length)); + } +} + +function makeContainer(typeName?: string, current?: ScvdNode): RefContainer { + const base = current ?? new FakeBase(typeName); + return { + base, + current: base, + valueType: undefined + }; +} + +function makeEvalInterface(symbolMap: Map, memoryMap: Map) { + const memHost = {} as unknown as MemoryHost; + const regHost = {} as unknown as RegisterHost; + const debugTarget = new FakeDebugTarget(symbolMap, memoryMap) as unknown as ScvdDebugTarget; + const formatter = new ScvdFormatSpecifier(); + return new ScvdEvalInterface(memHost, regHost, debugTarget, formatter); +} + +describe('ScvdEvalInterface.formatPrintf (CMSIS-View value_output)', () => { + const symbolMap = new Map([ + [0x1000, 'MySym'] + ]); + const memoryMap = new Map([ + [0x10, new Uint8Array([192, 168, 0, 1])], // IPv4 + [0x20, new Uint8Array([0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13])], // IPv6 + [0x24, new Uint8Array([0x2a, 0x00, 0x0e, 0xe0, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13])], // IPv6 spec example 2a00:ee0:d::13 + [0x30, new Uint8Array([0x1E, 0x30, 0x6C, 0xA2, 0x45, 0x5F])], // MAC + [0x40, new Uint8Array([0x4E, 0x61, 0x6D, 0x65, 0x00])], // "Name\0" + [0x50, new Uint8Array([0x55, 0x00, 0x53, 0x00, 0x42, 0x00, 0x00, 0x00])] // "USB" wide + ]); + + const scvd = makeEvalInterface(symbolMap, memoryMap); + + it('formats %d (signed decimal)', async () => { + const out = await scvd.formatPrintf('d', 42, makeContainer('int32_t')); + expect(out).toBe('42'); + }); + + it('formats %u (unsigned decimal)', async () => { + const out = await scvd.formatPrintf('u', -1, makeContainer('uint32_t')); + expect(out).toBe('4294967295'); + }); + + it('truncates floats for %u', async () => { + const out = await scvd.formatPrintf('u', 9.8, makeContainer('uint32_t')); + expect(out).toBe('9'); + }); + + it('truncates floats for %d', async () => { + const out = await scvd.formatPrintf('d', 7.9, makeContainer('int32_t')); + expect(out).toBe('7'); + }); + + it('formats %x (hex)', async () => { + const out = await scvd.formatPrintf('x', 0x1a, makeContainer('uint32_t')); + expect(out).toBe('0x1a'); + }); + + it('truncates floats for %x', async () => { + const out = await scvd.formatPrintf('x', 7.9, makeContainer('int32_t')); + expect(out).toBe('0x7'); + }); + + it('formats %x (hex) for 64-bit values (no NaN)', async () => { + const out = await scvd.formatPrintf('x', BigInt('0x123456789abcdef0'), makeContainer('uint64_t')); + expect(out).toBe('0x123456789abcdef0'); + }); + + it('formats %t (text from literal)', async () => { + const out = await scvd.formatPrintf('t', 'Status OK', makeContainer()); + expect(out).toBe('Status OK'); + }); + + it('formats %C as symbol name when available', async () => { + const out = await scvd.formatPrintf('C', 0x1000, makeContainer()); + expect(out).toBe('MySym'); + }); + + it('formats %S as symbol name when available', async () => { + const out = await scvd.formatPrintf('S', 0x1000, makeContainer()); + expect(out).toBe('MySym'); + }); + + it('formats %E using enum text', async () => { + const member = new FakeMember(); + const container: RefContainer = { base: member, current: member, valueType: undefined }; + const out = await scvd.formatPrintf('E', 2, container); + expect(out).toBe('ENUM_READY'); + }); + + it('formats %I (IPv4) from memory bytes', async () => { + const out = await scvd.formatPrintf('I', 0x10, makeContainer()); + expect(out).toBe('192.168.0.1'); + }); + + it('formats %J (IPv6) from memory bytes', async () => { + const out = await scvd.formatPrintf('J', 0x20, makeContainer()); + expect(out).toBe('2001:db8::13'); + }); + + it('formats %J (IPv6) using spec example address', async () => { + const out = await scvd.formatPrintf('J', 0x24, makeContainer()); + expect(out).toBe('2a00:ee0:d::13'); + }); + + it('formats %M (MAC) from memory bytes', async () => { + const out = await scvd.formatPrintf('M', 0x30, makeContainer()); + expect(out).toBe('1E-30-6C-A2-45-5F'); + }); + + it('formats %N (string address)', async () => { + const out = await scvd.formatPrintf('N', 0x40, makeContainer()); + expect(out).toBe('Name'); + }); + + it('formats %T as float for floating types', async () => { + const out = await scvd.formatPrintf('T', 3.14159, makeContainer('float')); + expect(out).toBe('3.142'); + }); + + it('formats %T as hex for integer types', async () => { + const out = await scvd.formatPrintf('T', 26, makeContainer('uint32_t')); + expect(out).toBe('0x1a'); + }); + + it('formats %U (wide string)', async () => { + const out = await scvd.formatPrintf('U', 0x50, makeContainer()); + expect(out).toBe('USB'); + }); + + it('formats %% literal percent', async () => { + const out = await scvd.formatPrintf('%', 'ignored', makeContainer()); + expect(out).toBe('%'); + }); + + it('returns placeholder for unknown spec', async () => { + const out = await scvd.formatPrintf('Z', 123, makeContainer()); + expect(out).toBe(''); + }); + + it('uses default 32-bit padding for array-like containers', async () => { + // Simulate an array container by returning a large array size + const arrayLike = new FakeBase('uint8_t'); + arrayLike.getArraySize = async () => 16; + const out = await scvd.formatPrintf('x', 0xAB, makeContainer(undefined, arrayLike)); + expect(out).toBe('0xab'); + }); + + it('forces 32-bit padding for _addr members', async () => { + const addrLike = new FakeBase('uint8_t'); + addrLike.name = '_addr'; + // even with odd target size, padding should be 32-bit + addrLike.getTargetSize = () => 1; + const out = await scvd.formatPrintf('x', 0x1, makeContainer(undefined, addrLike)); + expect(out).toBe('0x1'); + }); +}); + +describe('ScvdFormatSpecifier number output sample (CMSIS spec)', () => { + const fmt = new ScvdFormatSpecifier(); + const f = (spec: string, value: number | bigint, kind: FormatKind, bits: number) => + fmt.format(spec, value, { typeInfo: { kind, bits } }); + + it('unsigned integers (%d/%x/%T)', () => { + expect(f('d', 1, 'uint', 8)).toBe('1'); + expect(f('d', 0x2, 'uint', 16)).toBe('2'); + expect(f('d', 0x46, 'uint', 32)).toBe('70'); + expect(f('d', BigInt('0xFF12001612'), 'uint', 64)).toBe('1095518656018'); + + expect(f('x', 1, 'uint', 8)).toBe('0x1'); + expect(f('x', 0x2, 'uint', 16)).toBe('0x2'); + expect(f('x', 0x46, 'uint', 32)).toBe('0x46'); + expect(f('x', BigInt('0xFF12001612'), 'uint', 64)).toBe('0xff12001612'); + + expect(f('T', 1, 'uint', 8)).toBe('0x1'); + expect(f('T', 0x2, 'uint', 16)).toBe('0x2'); + expect(f('T', 0x46, 'uint', 32)).toBe('0x46'); + expect(f('T', BigInt('0xFF12001612'), 'uint', 64)).toBe('0xff12001612'); + }); + + it('signed integers (%d/%x/%T)', () => { + expect(f('d', 1, 'int', 8)).toBe('1'); + expect(f('d', -2, 'int', 16)).toBe('-2'); + expect(f('d', 46, 'int', 32)).toBe('46'); + expect(f('d', -6899123456, 'int', 64)).toBe('-6899123456'); + + expect(f('x', 1, 'int', 8)).toBe('0x1'); + expect(f('x', -2, 'int', 16)).toBe('0xfffffffe'); + expect(f('x', 46, 'int', 32)).toBe('0x2e'); + expect(f('x', -6899123456, 'int', 64)).toBe('0x64c7bb00'); + + expect(f('T', 1, 'int', 8)).toBe('0x1'); + expect(f('T', -2, 'int', 16)).toBe('0xfffffffe'); + expect(f('T', 46, 'int', 32)).toBe('0x2e'); + expect(f('T', -6899123456, 'int', 64)).toBe('0x64c7bb00'); + }); + + it('floating point (%d/%x/%T)', () => { + expect(f('d', 3.14156, 'float', 32)).toBe('3'); + expect(f('d', 15300.6711123, 'float', 64)).toBe('15300'); + + expect(f('x', 3.14156, 'float', 32)).toBe('0x3'); + expect(f('x', 15300.6711123, 'float', 64)).toBe('0x3bc4'); + + expect(f('T', 3.14156, 'float', 32)).toBe('3.142'); + expect(f('T', 15300.6711123, 'float', 64)).toBe('15300.7'); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.scalarInfo.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.scalarInfo.test.ts new file mode 100644 index 00000000..1bb64eae --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.scalarInfo.test.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Tests for scalar info derivation (padding/bit-width) in ScvdEvalInterface. + * Integration test for ScvdEvalInterface.scalarInfo. + */ + +import { ScvdEvalInterface } from '../../../../scvd-eval-interface'; +import { FormatTypeInfo } from '../../../../model/scvd-format-specifier'; +import { ScvdFormatSpecifier } from '../../../../model/scvd-format-specifier'; +import { RefContainer } from '../../../../parser-evaluator/model-host'; +import { MemoryHost } from '../../../../data-host/memory-host'; +import { RegisterHost } from '../../../../data-host/register-host'; +import { ScvdDebugTarget } from '../../../../scvd-debug-target'; +import { ScvdNode } from '../../../../model/scvd-node'; + +class ScalarBase extends ScvdNode { + constructor(private readonly typeName?: string, private readonly size?: number, private readonly arrayCount?: number) { + super(undefined); + } + public override getValueType(): string | undefined { + return this.typeName; + } + public override getTargetSize(): number | undefined { + return this.size; + } + public override async getArraySize(): Promise { + return this.arrayCount; + } +} + +function makeEval() { + const memHost = {} as unknown as MemoryHost; + const regHost = {} as unknown as RegisterHost; + const debugTarget = {} as unknown as ScvdDebugTarget; + const formatter = new ScvdFormatSpecifier(); + return new ScvdEvalInterface(memHost, regHost, debugTarget, formatter); +} + +describe('ScvdEvalInterface.getScalarInfo padding rules', () => { + it('pads single scalars using target size when no bits provided', async () => { + const ev = makeEval(); + const base = new ScalarBase('uint32_t', 4, undefined); + const info = await (ev as unknown as { getScalarInfo(c: RefContainer): Promise }) + .getScalarInfo({ base, current: base, valueType: undefined } as RefContainer); + expect(info).toBeDefined(); + expect(info!.bits).toBe(32); + }); + + it('does not inflate bits by array length and uses 32-bit default for arrays', async () => { + const ev = makeEval(); + const base = new ScalarBase('uint8_t', 1, 128); // array length 128, element 1 byte + const info = await (ev as unknown as { getScalarInfo(c: RefContainer): Promise }) + .getScalarInfo({ base, current: base, valueType: undefined } as RefContainer); + expect(info).toBeDefined(); + expect(info!.bits).toBe(32); // default array padding + }); + + it('respects explicit 64-bit scalar widths but caps extremely large sizes', async () => { + const ev = makeEval(); + const base = new ScalarBase('uint64_t', 16, undefined); // pretend 128-bit size + const info = await (ev as unknown as { getScalarInfo(c: RefContainer): Promise }) + .getScalarInfo({ base, current: base, valueType: undefined } as RefContainer); + expect(info).toBeDefined(); + expect(info!.bits).toBe(64); + }); + + it('defaults unknown wide types to 32-bit padding', async () => { + const ev = makeEval(); + const base = new ScalarBase(undefined, 8, undefined); // unknown type, 8-byte size + const info = await (ev as unknown as { getScalarInfo(c: RefContainer): Promise }) + .getScalarInfo({ base, current: base, valueType: undefined } as RefContainer); + expect(info).toBeDefined(); + expect(info!.bits).toBe(32); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.test.ts new file mode 100644 index 00000000..1a262eb7 --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/eval-interface/scvd-eval-interface.test.ts @@ -0,0 +1,104 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ScvdEvalInterface. + */ + +import { ScvdEvalInterface } from '../../../../scvd-eval-interface'; +import { MemoryHost } from '../../../../data-host/memory-host'; +import { RegisterHost } from '../../../../data-host/register-host'; +import { ScvdFormatSpecifier } from '../../../../model/scvd-format-specifier'; +import { ScvdDebugTarget } from '../../../../scvd-debug-target'; +import { RefContainer } from '../../../../parser-evaluator/model-host'; +import { ScvdNode } from '../../../../model/scvd-node'; + +const makeStubBase = (name: string): ScvdNode => ({ + name, + getSymbol: jest.fn(), + getMember: jest.fn(), + getDisplayLabel: jest.fn().mockReturnValue(name), + getValueType: jest.fn(), +} as unknown as ScvdNode); + +const makeContainer = (name: string, widthBytes: number, offsetBytes = 0): RefContainer => ({ + base: makeStubBase(name), + anchor: makeStubBase(name), + current: makeStubBase(name), + offsetBytes, + widthBytes, + valueType: undefined, +}); + +describe('ScvdEvalInterface', () => { + it('routes intrinsic calls to debugTarget/registers/memHost', async () => { + const memHost = new MemoryHost(); + const regCache = { read: jest.fn().mockReturnValue(7) } as unknown as RegisterHost; + const debugTarget = { + findSymbolAddress: jest.fn().mockResolvedValue(0x1234), + findSymbolNameAtAddress: jest.fn().mockResolvedValue('sym'), + calculateMemoryUsage: jest.fn().mockReturnValue(0xabcd), + getSymbolSize: jest.fn().mockResolvedValue(undefined), + getNumArrayElements: jest.fn().mockResolvedValue(3), + getTargetIsRunning: jest.fn().mockResolvedValue(true), + readUint8ArrayStrFromPointer: jest.fn().mockResolvedValue(new Uint8Array([65, 66])), + } as unknown as ScvdDebugTarget; + const fmt = new ScvdFormatSpecifier(); + const host = new ScvdEvalInterface(memHost, regCache, debugTarget, fmt); + + expect(await host.__FindSymbol('foo')).toBe(0x1234); + expect(await host.__GetRegVal('r0')).toBe(7); + expect(await host.__Symbol_exists('foo')).toBe(1); + expect(await host.__CalcMemUsed(1, 2, 3, 4)).toBe(0xabcd); + expect(await host.__size_of('arr')).toBe(3); + expect(await host.__Running()).toBe(1); + }); + + it('formats printf values and falls back to string', async () => { + const memHost = new MemoryHost(); + const regCache = { read: jest.fn() } as unknown as RegisterHost; + const debugTarget = { + findSymbolAddress: jest.fn(), + findSymbolNameAtAddress: jest.fn().mockResolvedValue('sym'), + getSymbolSize: jest.fn().mockResolvedValue(undefined), + getNumArrayElements: jest.fn().mockResolvedValue(undefined), + getTargetIsRunning: jest.fn(), + readUint8ArrayStrFromPointer: jest.fn(), + } as unknown as ScvdDebugTarget; + const fmt = new ScvdFormatSpecifier(); + const host = new ScvdEvalInterface(memHost, regCache, debugTarget, fmt); + + const container = makeContainer('v', 4); + + expect(await host.formatPrintf('d', 42, container)).toBe('42'); + expect(await host.formatPrintf('S', 0x1000, container)).toBe('sym'); + expect(await host.formatPrintf('?', true as unknown as number, container)).toBe(''); + }); + + it('readValue/writeValue interop with cache', async () => { + const memHost = new MemoryHost(); + const regCache = { read: jest.fn() } as unknown as RegisterHost; + const debugTarget = { getSymbolSize: jest.fn(), getNumArrayElements: jest.fn() } as unknown as ScvdDebugTarget; + const fmt = new ScvdFormatSpecifier(); + const host = new ScvdEvalInterface(memHost, regCache, debugTarget, fmt); + + const container = makeContainer('num', 4); + await host.writeValue(container, 0xdeadbeef); + expect(await host.readValue(container)).toBe(0xdeadbeef >>> 0); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-basic.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-basic.test.ts new file mode 100644 index 00000000..06d65bf2 --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-basic.test.ts @@ -0,0 +1,244 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for EvaluatorBasic. + */ + +import { EvalContext, evaluateParseResult } from '../../../../parser-evaluator/evaluator'; +import type { EvalValue, RefContainer } from '../../../../parser-evaluator/model-host'; +import type { FullDataHost } from '../../helpers/full-data-host'; +import { parseExpression } from '../../../../parser-evaluator/parser'; +import { ScvdNode } from '../../../../model/scvd-node'; + +type SymbolDef = { + value?: EvalValue; + members?: Record; + elements?: Record; + addr?: number; +}; + +// Loaded from static test fixture (path is fixed at build time). +type EvaluatorCase = { + expr: string; + expected: number | string | undefined; + symbols?: Record; + checkSymbol?: string; + expectedSymbol?: number | string | undefined; +}; + +type EvaluatorCasesFile = { + _meta: { format: string; copyright: string; generatedWith?: string }; + cases: EvaluatorCase[]; +}; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const fixture: EvaluatorCasesFile | EvaluatorCase[] = require('../../testfiles/evaluator-basic.json'); +const cases: EvaluatorCase[] = Array.isArray(fixture) ? fixture : fixture.cases; + +class MockRef extends ScvdNode { + value: EvalValue | undefined; + members: Map = new Map(); + elements: Map = new Map(); + addr: number | undefined; + + constructor(def?: SymbolDef, parent?: MockRef) { + super(parent); + this.value = def?.value; + this.addr = def?.addr; + if (def?.members) { + for (const [name, child] of Object.entries(def.members)) { + this.members.set(name, buildRef(child, this)); + } + } + if (def?.elements) { + for (const [index, child] of Object.entries(def.elements)) { + const idx = Number(index); + this.elements.set(Number.isFinite(idx) ? idx : 0, buildRef(child, this)); + } + } + } +} + +function buildRef(def?: SymbolDef, parent?: MockRef): MockRef { + return new MockRef(def, parent); +} + +class MockHost implements FullDataHost { + readonly root: MockRef; + private readonly symbols = new Map(); + private readonly regValues = new Map([['r0', 7]]); + private readonly symbolOffsets = new Map([['memberA', 12]]); + + constructor(symbols?: Record) { + this.root = new MockRef(); + const baseSymbols: Record = { + symA: { value: 0, addr: 0x1234 }, + foo: { value: 1 }, + }; + const merged = { ...baseSymbols, ...(symbols ?? {}) }; + for (const [name, def] of Object.entries(merged)) { + this.symbols.set(name, buildRef(def, this.root)); + } + } + + private resolveElement(ref: MockRef | undefined, index?: number): MockRef | undefined { + if (!ref) { + return undefined; + } + if (index !== undefined && ref.elements.size > 0) { + return ref.elements.get(index); + } + return ref; + } + + public async resolveColonPath(): Promise { + return undefined; + } + + public async getSymbolRef(_container: RefContainer, name: string, _forWrite?: boolean): Promise { + return this.symbols.get(name); + } + + public async getMemberRef(container: RefContainer, property: string, _forWrite?: boolean): Promise { + const base = this.resolveElement(container.current as MockRef, container.index); + return base?.members.get(property); + } + + public async readValue(container: RefContainer): Promise { + const ref = + (container.member as MockRef | undefined) ?? + this.resolveElement(container.current as MockRef, container.index) ?? + (container.anchor as MockRef | undefined); + return ref?.value; + } + + public async writeValue(container: RefContainer, value: EvalValue): Promise { + const ref = + (container.member as MockRef | undefined) ?? + this.resolveElement(container.current as MockRef, container.index) ?? + (container.anchor as MockRef | undefined); + if (!ref) { + return undefined; + } + ref.value = value; + return value; + } + + public async getElementStride(_ref: MockRef): Promise { + return 1; + } + + public async getMemberOffset(_base: MockRef, _member: MockRef): Promise { + return undefined; + } + + public async getElementRef(ref: MockRef): Promise { + return this.resolveElement(ref); + } + + public async getByteWidth(): Promise { + return 4; + } + + public async _count(container: RefContainer): Promise { + const ref = this.resolveElement(container.current as MockRef, container.index); + if (!ref) { + return undefined; + } + if (ref.elements.size > 0) { + return ref.elements.size; + } + if (ref.members.size > 0) { + return ref.members.size; + } + if (typeof ref.value === 'string') { + return ref.value.length; + } + return 0; + } + + public async _addr(container: RefContainer): Promise { + const ref = this.resolveElement(container.current as MockRef, container.index); + return ref?.addr ?? 0; + } + + public async __Running(): Promise { + return 1; + } + + public async __GetRegVal(reg: string): Promise { + return this.regValues.get(reg); + } + + public async __FindSymbol(symbol: string): Promise { + const ref = this.symbols.get(symbol); + if (ref?.addr !== undefined) { + return ref.addr; + } + if (typeof ref?.value === 'number') { + return ref.value; + } + return undefined; + } + + public async __CalcMemUsed(a: number, b: number, c: number, d: number): Promise { + return (a >>> 0) + (b >>> 0) + (c >>> 0) + (d >>> 0); + } + + public async __size_of(symbol: string): Promise { + return this.symbols.has(symbol) ? 4 : undefined; + } + + public async __Symbol_exists(symbol: string): Promise { + return this.symbols.has(symbol) ? 1 : 0; + } + + public async __Offset_of(_container: RefContainer, typedefMember: string): Promise { + return this.symbolOffsets.get(typedefMember); + } + + public async formatPrintf(): Promise { + return undefined; + } + + public async getValueType(): Promise { + return undefined; + } + + public getSymbolValue(name: string): EvalValue | undefined { + return this.symbols.get(name)?.value; + } +} + +describe('evaluator', () => { + it.each(cases)('evaluates %s', async testCase => { + const host = new MockHost(testCase.symbols); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression(testCase.expr, false); + + expect(pr.diagnostics).toHaveLength(0); + + const result = await evaluateParseResult(pr, ctx); + expect(result).toEqual(testCase.expected); + + if (testCase.checkSymbol) { + expect(host.getSymbolValue(testCase.checkSymbol)).toEqual(testCase.expectedSymbol); + } + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-datahost-hooks.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-datahost-hooks.test.ts new file mode 100644 index 00000000..72bd1eee --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-datahost-hooks.test.ts @@ -0,0 +1,250 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for EvaluatorDatahostHooks. + */ + +import { EvalContext, evaluateParseResult } from '../../../../parser-evaluator/evaluator'; +import type { RefContainer, EvalValue } from '../../../../parser-evaluator/model-host'; +import type { FullDataHost } from '../../helpers/full-data-host'; +import { parseExpression } from '../../../../parser-evaluator/parser'; +import { ScvdNode } from '../../../../model/scvd-node'; + +class BasicRef extends ScvdNode { + constructor(parent?: ScvdNode) { + super(parent); + } +} + +class HookHost implements FullDataHost { + readonly root = new BasicRef(); + readonly arrRef = new BasicRef(this.root); + readonly elemRef = new BasicRef(this.arrRef); + readonly fieldRef = new BasicRef(this.elemRef); + lastFormattingContainer: RefContainer | undefined; + + private readonly values = new Map([ + [10, 99], // offsetBytes for arr[2].field + [6, 0xab], // offsetBytes for arr[1].field in printf path + ]); + private readonly disablePrintfOverride: boolean; + + calls: Record = {}; + + constructor(opts?: { disablePrintfOverride?: boolean }) { + this.disablePrintfOverride = opts?.disablePrintfOverride ?? false; + } + + private tick(name: string) { + // eslint-disable-next-line security/detect-object-injection -- false positive: controlled key accumulation for test bookkeeping + this.calls[name] = (this.calls[name] ?? 0) + 1; + } + + public async getSymbolRef(_container: RefContainer, name: string): Promise { + this.tick('getSymbolRef'); + if (name === 'arr') { + return this.arrRef; + } + return undefined; + } + + public async getMemberRef(_container: RefContainer, property: string): Promise { + this.tick('getMemberRef'); + if (property === 'field') { + return this.fieldRef; + } + // allow colon-path anchor to succeed + if (property === 'dummy') { + return this.fieldRef; + } + return undefined; + } + + public async getElementStride(_ref: ScvdNode): Promise { + this.tick('getElementStride'); + return 4; + } + + public async getMemberOffset(_base: ScvdNode, _member: ScvdNode): Promise { + this.tick('getMemberOffset'); + return 2; + } + + public async getElementRef(): Promise { + this.tick('getElementRef'); + return this.elemRef; + } + + public async getByteWidth(): Promise { + this.tick('getByteWidth'); + return 4; + } + + public setValueAt(offset: number, value: EvalValue): void { + this.values.set(offset, value); + } + + public async resolveColonPath(_container: RefContainer, parts: string[]): Promise { + this.tick('resolveColonPath'); + return parts.length * 100; // simple sentinel + } + + public async readValue(container: RefContainer): Promise { + this.tick('readValue'); + const off = container.offsetBytes ?? 0; + return this.values.get(off); + } + + public async writeValue(_container: RefContainer, value: EvalValue): Promise { + this.tick('writeValue'); + return value; + } + + public async _count(): Promise { + this.tick('_count'); + return undefined; + } + + public async _addr(): Promise { + this.tick('_addr'); + return undefined; + } + + public async formatPrintf(spec: string, value: EvalValue, container: RefContainer): Promise { + this.tick('formatPrintf'); + this.lastFormattingContainer = container; + if (this.disablePrintfOverride) { + if (value instanceof Uint8Array && spec === 'M') { + return Array.from(value.subarray(0, 6)) + .map(b => b.toString(16).padStart(2, '0').toUpperCase()) + .join('-'); + } + return undefined; + } + return `fmt-${spec}-${value}`; + } + + public async getValueType(): Promise { + this.tick('getValueType'); + return undefined; + } + + public async __GetRegVal(): Promise { + this.tick('__GetRegVal'); + return undefined; + } + + public async __FindSymbol(): Promise { + this.tick('__FindSymbol'); + return undefined; + } + + public async __CalcMemUsed(): Promise { + this.tick('__CalcMemUsed'); + return undefined; + } + + public async __size_of(): Promise { + this.tick('__size_of'); + return undefined; + } + + public async __Symbol_exists(): Promise { + this.tick('__Symbol_exists'); + return undefined; + } + + public async __Offset_of(): Promise { + this.tick('__Offset_of'); + return undefined; + } + + public async __Running(): Promise { + this.tick('__Running'); + return undefined; + } +} + +describe('evaluator data host hooks', () => { + it('uses stride/offset/element helpers for array member reads', async () => { + const host = new HookHost(); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression('arr[2].field', false); + + const out = await evaluateParseResult(pr, ctx); + expect(out).toBe(99); + expect(host.calls.getElementStride).toBe(1); + expect(host.calls.getElementRef).toBe(1); + expect(host.calls.getMemberOffset).toBe(1); + expect(host.calls.getByteWidth).toBeGreaterThanOrEqual(1); + }); + + it('calls resolveColonPath for colon expressions', async () => { + const host = new HookHost(); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression('foo:bar:baz', false); + + const out = await evaluateParseResult(pr, ctx); + expect(out).toBe(300); // 3 parts * 100 + expect(host.calls.resolveColonPath).toBe(1); + }); + + it('honors printf formatting override', async () => { + const host = new HookHost(); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression('val=%x[arr[1].field]', true); + + const out = await evaluateParseResult(pr, ctx); + expect(out).toBe('val=fmt-x-171'); + expect(host.calls.formatPrintf).toBe(1); + }); + + it('recovers reference containers for printf subexpressions', async () => { + const host = new HookHost(); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression('val=%x[arr[1].field + 1]', true); + + await evaluateParseResult(pr, ctx); + expect(host.calls.formatPrintf).toBe(1); + expect(host.lastFormattingContainer?.current).toBe(host.fieldRef); + }); + + it('does not recover containers for constant-only branches', async () => { + const host = new HookHost(); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression('val=%x[false ? arr[1].field : 5]', true); + + await evaluateParseResult(pr, ctx); + expect(host.calls.formatPrintf).toBe(1); + expect(host.lastFormattingContainer?.current).toBeUndefined(); + }); + + it('passes cached Uint8Array values to printf', async () => { + const host = new HookHost({ disablePrintfOverride: true }); + // Override the value at offset 6 (arr[1].field) with a 6-byte MAC + host.setValueAt(6, new Uint8Array([0x1e, 0x30, 0x6c, 0xa2, 0x45, 0x5f])); + const ctx = new EvalContext({ data: host, container: host.root }); + const pr = parseExpression('mac=%M[arr[1].field]', true); + + const out = await evaluateParseResult(pr, ctx); + expect(out).toBe('mac=1E-30-6C-A2-45-5F'); + expect(host.calls.formatPrintf).toBe(1); + expect(host.lastFormattingContainer?.current).toBe(host.fieldRef); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator.guards.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator.guards.test.ts new file mode 100644 index 00000000..25ec1036 --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator.guards.test.ts @@ -0,0 +1,301 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for Evaluator.guardCoverage. + */ + +import * as evaluator from '../../../../parser-evaluator/evaluator'; +import { EvalContext } from '../../../../parser-evaluator/evaluator'; +import type { ASTNode, AssignmentExpression, CallExpression, UnaryExpression, BinaryExpression, ColonPath, FormatSegment } from '../../../../parser-evaluator/parser'; +import type { ModelHost } from '../../../../parser-evaluator/model-host'; +import { ScvdNode } from '../../../../model/scvd-node'; +import type { EvalValue, RefContainer, ScalarType } from '../../../../parser-evaluator/ref-container'; + +class BareNode extends ScvdNode { + constructor() { + super(undefined); + } + public override getSymbol(_name: string): ScvdNode | undefined { return undefined; } + public getMember(_property: string): ScvdNode | undefined { return undefined; } + public async setValue(): Promise { return undefined; } + public async getValue(): Promise { return undefined; } +} + +class StubHost implements ModelHost { + constructor(private readonly memberRef?: ScvdNode, private readonly memberOffset?: number, private readonly byteWidth?: number) {} + async getSymbolRef(container: RefContainer): Promise { + container.current = container.base; + return container.base; + } + async getMemberRef(container: RefContainer, _property: string): Promise { + container.current = this.memberRef; + return this.memberRef; + } + async readValue(): Promise { return 0; } + async writeValue(_container: RefContainer, value: EvalValue): Promise { return value; } + async resolveColonPath(): Promise { return undefined; } + async getElementStride(): Promise { return 0; } + async getMemberOffset(): Promise { return this.memberOffset; } + async getByteWidth(): Promise { return this.byteWidth; } + async getElementRef(): Promise { return undefined; } + async getValueType(): Promise { return undefined; } +} + +function makeCtx(host: ModelHost): EvalContext { + const base = new BareNode(); + return new EvalContext({ data: host as never, container: base }); +} + +describe('evaluator guards', () => { + const asAny = evaluator.__test__ as Record; + + it('covers findReferenceNode guards and non-reference path', () => { + const fn = asAny.findReferenceNode as (n: ASTNode | undefined) => ASTNode | undefined; + expect(fn(undefined)).toBeUndefined(); + expect(fn({ kind: 'Unknown', start: 0, end: 0 } as unknown as ASTNode)).toBeUndefined(); + + const callNode: CallExpression = { kind: 'CallExpression', callee: { kind: 'Identifier', name: 'fn', start: 0, end: 0 }, args: [], start: 0, end: 0 }; + expect(fn(callNode)).toBe(callNode.callee); + + const callWithArg: CallExpression = { + kind: 'CallExpression', + callee: { kind: 'Identifier', name: 'fn', start: 0, end: 0 }, + args: [{ kind: 'Identifier', name: 'arg', start: 0, end: 0 }], + start: 0, + end: 0, + }; + expect(fn(callWithArg)).toBe(callWithArg.args[0]); + + const assignNode: AssignmentExpression = { kind: 'AssignmentExpression', operator: '=', left: { kind: 'Identifier', name: 'x', start: 0, end: 0 }, right: { kind: 'Identifier', name: 'y', start: 0, end: 0 }, start: 0, end: 0 }; + expect(fn(assignNode)).toBe(assignNode.right); + + const evalPoint: ASTNode = { kind: 'EvalPointCall', intrinsic: '__Running', callee: { kind: 'Identifier', name: '__Running', start: 0, end: 0 }, args: [], start: 0, end: 0 } as unknown as ASTNode; + expect(fn(evalPoint)).toBe((evalPoint as CallExpression).callee); + + const evalPointWithArg: ASTNode = { + kind: 'EvalPointCall', + intrinsic: '__Running', + callee: { kind: 'Identifier', name: '__Running', start: 0, end: 0 }, + args: [{ kind: 'Identifier', name: 'arg', start: 0, end: 0 }], + start: 0, + end: 0, + } as unknown as ASTNode; + expect(fn(evalPointWithArg)).toBe((evalPointWithArg as CallExpression).args[0]); + + const printfAst: ASTNode = { + kind: 'PrintfExpression', + segments: [{ kind: 'TextSegment', text: 'only text', start: 0, end: 0 }], + resultType: 'string', + start: 0, + end: 0, + } as ASTNode; + expect(fn(printfAst)).toBeUndefined(); + + const capture = asAny.captureContainerForReference as (n: ASTNode, ctx: EvalContext) => Promise; + const ctx = makeCtx(new StubHost()); + expect(capture({ kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 1 } as ASTNode, ctx)).resolves.toBeUndefined(); + }); + + it('covers asNumber fallbacks', () => { + const asNumber = asAny.asNumber as (v: unknown) => number; + expect(asNumber('not-a-number')).toBe(0); + expect(asNumber('')).toBe(0); + expect(asNumber(false)).toBe(0); + expect(asNumber(1n)).toBe(1); + expect(asNumber(true)).toBe(1); + }); + + it('covers integerDiv/mod zero and unsigned/bigint paths', () => { + const integerDiv = asAny.integerDiv as (a: number | bigint, b: number | bigint, unsigned: boolean) => number | bigint; + const integerMod = asAny.integerMod as (a: number | bigint, b: number | bigint, unsigned: boolean) => number | bigint; + expect(() => integerDiv(1n, 0n, false)).toThrow('Division by zero'); + expect(() => integerDiv(1, 0, true)).toThrow('Division by zero'); + expect(() => integerDiv(1, 0, false)).toThrow('Division by zero'); + expect(() => integerMod(1n, 0n, false)).toThrow('Division by zero'); + expect(() => integerMod(1, 0, true)).toThrow('Division by zero'); + expect(() => integerMod(1, 0, false)).toThrow('Division by zero'); + // NaN bypasses the first guard but is coerced to 0 inside the signed path + expect(() => integerDiv(1, Number.NaN, false)).toThrow('Division by zero'); + expect(() => integerMod(1, Number.NaN, false)).toThrow('Division by zero'); + expect(integerDiv(8n, 2n, false)).toBe(4n); + expect(integerMod(9n, 2n, false)).toBe(1n); + expect(integerDiv(8, 2, true)).toBe(4); + expect(integerMod(9, 2, true)).toBe(1); + // Signed numeric path (non-unsigned) to exercise the signed guard blocks + expect(integerDiv(6, 3, false)).toBe(2); + expect(integerMod(7, 3, false)).toBe(1); + // malformed inputs to hit deeper zero checks + expect(() => integerDiv(1n, '0' as unknown as number, false)).toThrow('Division by zero'); + expect(() => integerDiv(1, '0' as unknown as number, true)).toThrow('Division by zero'); + expect(() => integerMod(1n, '0' as unknown as number, false)).toThrow('Division by zero'); + expect(() => integerMod(1, '0' as unknown as number, true)).toThrow('Division by zero'); + }); + + it('covers evalArgsForIntrinsic error path', async () => { + const evalArgsForIntrinsic = asAny.evalArgsForIntrinsic as (name: string, args: ASTNode[], ctx: EvalContext) => Promise; + const ctx = makeCtx(new StubHost()); + await expect(evalArgsForIntrinsic('__FindSymbol', [{ kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 1 } as ASTNode], ctx)) + .rejects.toThrow('expects an identifier or string literal'); + }); + + it('covers getScalarTypeForContainer missing getValueType', async () => { + const getScalar = asAny.getScalarTypeForContainer as (ctx: EvalContext, c: RefContainer) => Promise; + const ctx = makeCtx({} as ModelHost); + ctx.container.valueType = undefined; + await expect(getScalar(ctx, ctx.container)).resolves.toBeUndefined(); + }); + + it('covers comparison helpers string/boolean/bigint paths', () => { + const eq = asAny.eqVals as (a: EvalValue, b: EvalValue) => boolean; + const lt = asAny.ltVals as (a: EvalValue, b: EvalValue) => boolean; + const lte = asAny.lteVals as (a: EvalValue, b: EvalValue) => boolean; + const gt = asAny.gtVals as (a: EvalValue, b: EvalValue) => boolean; + const gte = asAny.gteVals as (a: EvalValue, b: EvalValue) => boolean; + + expect(eq('1', 1)).toBe(true); + expect(lt(1n, 2n)).toBe(true); + expect(lte(2n, 2n)).toBe(true); + expect(gt(3n, 2n)).toBe(true); + expect(gte(2n, 2n)).toBe(true); + expect(eq('a', 'b')).toBe(false); + }); + + it('covers mustRef invalid targets and missing members', async () => { + const mustRef = asAny.mustRef as (node: ASTNode, ctx: EvalContext, forWrite: boolean) => Promise; + const ctx = makeCtx(new StubHost()); + await expect(mustRef({ kind: 'EvalPointCall' } as ASTNode, ctx, false)).rejects.toThrow('Invalid reference target.'); + + const memberCtx = makeCtx(new StubHost(undefined)); + const ma: ASTNode = { kind: 'MemberAccess', object: { kind: 'Identifier', name: 'root', start: 0, end: 0 }, property: 'missing', start: 0, end: 0 } as ASTNode; + await expect(mustRef(ma, memberCtx, false)).rejects.toThrow('Missing member \'missing\''); + + const arrCtx = makeCtx(new StubHost(undefined)); + const arrMember: ASTNode = { + kind: 'MemberAccess', + object: { kind: 'ArrayIndex', array: { kind: 'Identifier', name: 'arr', start: 0, end: 0 }, index: { kind: 'NumberLiteral', value: 0, raw: '0', valueType: 'number', constValue: 0, start: 0, end: 1 }, start: 0, end: 0 } as ASTNode, + property: 'missing', + start: 0, + end: 0, + }; + await expect(mustRef(arrMember, arrCtx, false)).rejects.toThrow('Missing member \'missing\''); + + const defaultCtx = makeCtx(new StubHost()); + await expect(mustRef({ kind: 'Unknown', start: 0, end: 0 } as unknown as ASTNode, defaultCtx, false)).rejects.toThrow('Invalid reference target.'); + }); + + it('covers colon path unresolved error', async () => { + const ctx = makeCtx(new StubHost()); + const colon: ColonPath = { kind: 'ColonPath', parts: ['a', 'b'], start: 0, end: 0 }; + await expect(evaluator.evalNode(colon as ASTNode, ctx)).rejects.toThrow('Unresolved colon path: a:b'); + }); + + it('covers unsupported operators and unknown node kinds', async () => { + const ctx = makeCtx(new StubHost()); + const badUnary = { kind: 'UnaryExpression', operator: '?' as unknown as UnaryExpression['operator'], argument: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 1 }, start: 0, end: 0 } as unknown as UnaryExpression; + await expect(evaluator.evalNode(badUnary, ctx)).rejects.toThrow('Unsupported unary operator ?'); + + const badAssign = { + kind: 'AssignmentExpression', + operator: '?=' as unknown as AssignmentExpression['operator'], + left: { kind: 'Identifier', name: 'x', start: 0, end: 0 }, + right: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 1 }, + start: 0, + end: 0, + } as unknown as AssignmentExpression; + await expect(evaluator.evalNode(badAssign, ctx)).rejects.toThrow('Unsupported assignment operator ?='); + + await expect(evaluator.evalNode({ kind: 'Unknown', start: 0, end: 0 } as unknown as ASTNode, ctx)).rejects.toThrow('Unhandled node kind: Unknown'); + }); + + it('covers formatValue bigint/NaN paths', async () => { + const formatValue = asAny.formatValue as (spec: FormatSegment['spec'], v: EvalValue, ctx?: EvalContext) => Promise; + const ctx = makeCtx(new StubHost()); + await expect(formatValue('d', 2n, ctx)).resolves.toBe('2'); + await expect(formatValue('u', -2n, ctx)).resolves.toBe('2'); + await expect(formatValue('x', 0xffn, ctx)).resolves.toBe('0xff'); + await expect(formatValue('x', Number.POSITIVE_INFINITY, ctx)).resolves.toBe('NaN'); + await expect(formatValue('?', 123, ctx)).resolves.toBe('123'); + await expect(formatValue('C', true, ctx)).resolves.toBe('true'); + }); + + it('covers mustRead undefined value', async () => { + class UndefinedReadHost extends StubHost { + async readValue(): Promise { return undefined; } + } + const ctx = makeCtx(new UndefinedReadHost()); + await expect(evaluator.evalNode({ kind: 'Identifier', name: 'x', start: 0, end: 0 }, ctx)).rejects.toThrow('Undefined value'); + }); + + it('covers unary bigint and bitwise paths plus >>> error', async () => { + const base = new BareNode(); + const ctx = new EvalContext({ data: new StubHost() as never, container: base }); + await expect(evaluator.evalNode({ kind: 'UnaryExpression', operator: '+', argument: { kind: 'NumberLiteral', value: 1n, raw: '1n', valueType: 'number', constValue: 1n, start: 0, end: 0 }, start: 0, end: 0 } as unknown as ASTNode, ctx)).resolves.toBe(1n); + await expect(evaluator.evalNode({ kind: 'UnaryExpression', operator: '-', argument: { kind: 'NumberLiteral', value: 2n, raw: '2n', valueType: 'number', constValue: 2n, start: 0, end: 0 }, start: 0, end: 0 } as unknown as ASTNode, ctx)).resolves.toBe(-2n); + await expect(evaluator.evalNode({ kind: 'UnaryExpression', operator: '~', argument: { kind: 'NumberLiteral', value: 1n, raw: '1n', valueType: 'number', constValue: 1n, start: 0, end: 0 }, start: 0, end: 0 } as unknown as ASTNode, ctx)).resolves.toBe(~1n); + await expect(evaluator.evalNode({ kind: 'UnaryExpression', operator: '~', argument: { kind: 'NumberLiteral', value: 3, raw: '3', valueType: 'number', constValue: 3, start: 0, end: 0 }, start: 0, end: 0 } as ASTNode, ctx)).resolves.toBe(((~(3 | 0)) >>> 0)); + const evalBinary = asAny.evalBinary as (n: BinaryExpression, ctx: EvalContext) => Promise; + await expect(evalBinary({ kind: 'BinaryExpression', operator: '>>>', left: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 0 }, right: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 0 }, start: 0, end: 0 } as BinaryExpression, ctx)).rejects.toThrow('Unsupported operator >>>'); + }); + + it('covers call-expression intrinsic and EvalPointCall missing intrinsic', async () => { + class IntrinsicHost extends StubHost { + async __GetRegVal(): Promise { return 7; } + } + const host = new IntrinsicHost(); + const ctx = makeCtx(host); + const callExpr: CallExpression = { + kind: 'CallExpression', + callee: { kind: 'Identifier', name: '__GetRegVal', start: 0, end: 0 }, + args: [{ kind: 'StringLiteral', value: 'r0', raw: '"r0"', valueType: 'string', constValue: 'r0', start: 0, end: 0 }], + start: 0, + end: 0, + }; + await expect(evaluator.evalNode(callExpr, ctx)).resolves.toBe(7); + + const missingEval: ASTNode = { kind: 'EvalPointCall', intrinsic: 'missing', callee: { kind: 'Identifier', name: 'missing', start: 0, end: 0 }, args: [], start: 0, end: 0 } as unknown as ASTNode; + await expect(evaluator.evalNode(missingEval, ctx)).rejects.toThrow('Missing intrinsic missing'); + }); + + it('covers TextSegment and unsupported binary operator throw', async () => { + const base = new BareNode(); + const ctx = new EvalContext({ data: new StubHost() as never, container: base }); + const textSeg: ASTNode = { kind: 'TextSegment', text: 'hi', start: 0, end: 0 } as ASTNode; + await expect(evaluator.evalNode(textSeg, ctx)).resolves.toBe('hi'); + + const evalBinary = asAny.evalBinary as (n: BinaryExpression, ctx: EvalContext) => Promise; + const badBin: BinaryExpression = { kind: 'BinaryExpression', operator: '**', left: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 0 }, right: { kind: 'NumberLiteral', value: 2, raw: '2', valueType: 'number', constValue: 2, start: 0, end: 0 }, start: 0, end: 0 }; + await expect(evalBinary(badBin, ctx)).rejects.toThrow('Unsupported binary operator **'); + + const plusString: BinaryExpression = { + kind: 'BinaryExpression', + operator: '+', + left: { kind: 'StringLiteral', value: 'a', raw: '"a"', valueType: 'string', constValue: 'a', start: 0, end: 0 }, + right: { kind: 'StringLiteral', value: 'b', raw: '"b"', valueType: 'string', constValue: 'b', start: 0, end: 0 }, + start: 0, + end: 0, + }; + await expect(evalBinary(plusString, ctx)).resolves.toBe('ab'); + }); + + it('covers normalizeEvaluateResult for null/boolean', () => { + const normalize = asAny.normalizeEvaluateResult as (v: EvalValue) => EvalValue | undefined; + expect(normalize(null as unknown as EvalValue)).toBeUndefined(); + expect(normalize(true)).toBe(1); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator.math.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator.math.test.ts new file mode 100644 index 00000000..7a481b51 --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator.math.test.ts @@ -0,0 +1,220 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Stress math across mixed scalar types using parsed ASTs. + * Integration test for Evaluator.math. + */ + +import { parseExpression } from '../../../../parser-evaluator/parser'; +import { EvalContext, evalNode, evaluateParseResult } from '../../../../parser-evaluator/evaluator'; +import type { EvalValue, RefContainer, ScalarType } from '../../../../parser-evaluator/model-host'; +import type { FullDataHost } from '../../helpers/full-data-host'; +import { ScvdNode } from '../../../../model/scvd-node'; + +class TypedNode extends ScvdNode { + public readonly typeName: string; + public value: EvalValue; + constructor(name: string, parent: ScvdNode | undefined, value: EvalValue, typeName: string) { + super(parent); + this.name = name; + this.value = value; + this.typeName = typeName; + } +} + +class MathHost implements FullDataHost { + constructor(private readonly values: Map) {} + + async resolveColonPath(): Promise { + return undefined; + } + async getSymbolRef(container: RefContainer, name: string): Promise { + const n = this.values.get(name); + container.current = n; + container.anchor = n; + return n; + } + async getMemberRef(): Promise { return undefined; } + async readValue(container: RefContainer): Promise { + return (container.current as TypedNode | undefined)?.value; + } + async writeValue(): Promise { return undefined; } + async getValueType(container: RefContainer): Promise { + const cur = container.current as TypedNode | undefined; + return cur?.typeName; + } + async getByteWidth(ref: ScvdNode): Promise { + const cur = ref as TypedNode | undefined; + if (!cur) { + return undefined; + } + const t = cur.typeName.toLowerCase(); + if (t.includes('64')) { + return 8; + } + if (t.includes('32') || t.includes('float')) { + return 4; + } + if (t.includes('16')) { + return 2; + } + return 1; + } + async getElementStride(_ref: ScvdNode): Promise { return 1; } + async getMemberOffset(_base: ScvdNode, _member: ScvdNode): Promise { return undefined; } + async getElementRef(ref: ScvdNode): Promise { return ref.getElementRef(); } + async __GetRegVal(): Promise { return undefined; } + async __FindSymbol(): Promise { return undefined; } + async __CalcMemUsed(): Promise { return undefined; } + async __size_of(): Promise { return undefined; } + async __Symbol_exists(): Promise { return undefined; } + async __Offset_of(): Promise { return undefined; } + async __Running(): Promise { return undefined; } + async _count(): Promise { return undefined; } + async _addr(): Promise { return undefined; } + async formatPrintf(): Promise { return undefined; } +} + +function makeHost(defs: Array<[string, EvalValue, string]>): { host: MathHost; base: TypedNode } { + const base = new TypedNode('base', undefined, 0, 'int32'); + const map = new Map(); + for (const [name, value, typeName] of defs) { + map.set(name, new TypedNode(name, base, value, typeName)); + } + return { host: new MathHost(map), base }; +} + +function evalParsed(expr: string, host: MathHost, base: TypedNode) { + const ctx = new EvalContext({ data: host, container: base }); + return evalNode(parseExpression(expr, false).ast, ctx); +} + +function evalParsedNormalized(expr: string, host: MathHost, base: TypedNode) { + const ctx = new EvalContext({ data: host, container: base }); + return evaluateParseResult(parseExpression(expr, false), ctx); +} + +describe('evaluator math mixing scalar kinds', () => { + it('handles integer arithmetic across sizes and signedness', async () => { + const { host, base } = makeHost([ + ['u8', 10, 'uint8'], + ['s8', -2, 'int8'], + ['u16', 1000, 'uint16'], + ['s16', -200, 'int16'], + ['u32', 0xFFFF_FFF0, 'uint32'], + ['s32', -123456, 'int32'], + ['u8ov', 250, 'uint8'], + ['u16ov', 0xFFFF, 'uint16'], + ['i8ov', 120, 'int8'], + ['i16ov', 0x7FFF, 'int16'], + ]); + + await expect(evalParsedNormalized('u8 + u16', host, base)).resolves.toBe(1010); + await expect(evalParsedNormalized('s8 + u8', host, base)).resolves.toBe(8); + await expect(evalParsedNormalized('u16 - s16', host, base)).resolves.toBe(1200); + await expect(evalParsedNormalized('u32 + s32', host, base)).resolves.toBe(4294843824); + await expect(evalParsedNormalized('u16 * u8', host, base)).resolves.toBe(10000); + await expect(evalParsedNormalized('u16 / u8', host, base)).resolves.toBe(100); + await expect(evalParsedNormalized('u16 % u8', host, base)).resolves.toBe(0); + await expect(evalParsedNormalized('u16 << 4', host, base)).resolves.toBe(16000); + await expect(evalParsedNormalized('u16 >> 3', host, base)).resolves.toBe(125); + await expect(evalParsedNormalized('s16 >> 3', host, base)).resolves.toBe(-25); + await expect(evalParsedNormalized('u8 & u16', host, base)).resolves.toBe(8); + await expect(evalParsedNormalized('u8 | u16', host, base)).resolves.toBe(1002); + await expect(evalParsedNormalized('u8 ^ u16', host, base)).resolves.toBe(994); + await expect(evalParsedNormalized('u8 < u16', host, base)).resolves.toBe(1); + await expect(evalParsedNormalized('u8 >= u16', host, base)).resolves.toBe(0); + + // overflow wrap for unsigned math + await expect(evalParsedNormalized('u8ov + 10', host, base)).resolves.toBe(4); + await expect(evalParsedNormalized('u8ov * 2', host, base)).resolves.toBe(244); + await expect(evalParsedNormalized('u16ov + 2', host, base)).resolves.toBe(1); + await expect(evalParsedNormalized('u16ov * 2', host, base)).resolves.toBe(0xFFFE); + + // signed overflow follows current evaluator sign-extension semantics + await expect(evalParsedNormalized('i8ov + i8ov', host, base)).resolves.toBe(-16); + await expect(evalParsedNormalized('i16ov + 1', host, base)).resolves.toBe(-32768); + await expect(evalParsedNormalized('u8ov & u32', host, base)).resolves.toBe(240); + await expect(evalParsedNormalized('u8ov | u32', host, base)).resolves.toBe(4294967290); + }); + + it('handles float/double with integers', async () => { + const { host, base } = makeHost([ + ['f', 2.5, 'float32'], + ['d', 1.25, 'double'], + ['i', 2, 'int32'], + ]); + + await expect(evalParsedNormalized('f + i', host, base)).resolves.toBeCloseTo(4.5); + await expect(evalParsedNormalized('d - f', host, base)).resolves.toBeCloseTo(-1.25); + await expect(evalParsedNormalized('f * d', host, base)).resolves.toBeCloseTo(3.125); + await expect(evalParsedNormalized('f / i', host, base)).resolves.toBeCloseTo(1.25); + }); + + it('handles 64-bit bigint math', async () => { + const { host, base } = makeHost([ + ['i64a', 2n ** 60n, 'int64'], + ['i64b', 3n, 'int64'], + ]); + + await expect(evalParsed('i64a + i64b', host, base)).resolves.toBe(1152921504606846979n); + await expect(evalParsed('i64a - i64b', host, base)).resolves.toBe(1152921504606846973n); + await expect(evalParsed('i64a >> 2', host, base)).resolves.toBe(288230376151711744n); + await expect(evalParsed('i64a & i64b', host, base)).resolves.toBe(0n); + }); + + it('survives complex mixed expressions', async () => { + const { host, base } = makeHost([ + ['u8', 3, 'uint8'], + ['u16', 5, 'uint16'], + ['f', 1.5, 'float32'], + ['d', 2.0, 'double'], + ['i64', 10n, 'uint64'], + ['u32', 0xFFFF_FFFF, 'uint32'], + ]); + + await expect(evalParsedNormalized('(u8 + u16) * 2 + (f - d)', host, base)).resolves.toBeCloseTo(15.5); + await expect(evalParsed('i64 + u8 + u16', host, base)).resolves.toBe(18n); + await expect(evalParsedNormalized('((u8 << 2) & 0xF) | (u16 % 3)', host, base)).resolves.toBe(12 | 2); + await expect(evalParsedNormalized('u32 + u8', host, base)).resolves.toBe(2); + await expect(evalParsedNormalized('(u8 + u8) << 1', host, base)).resolves.toBe(12); // wraps to 8-bit after shift + }); + + it('truncates shift results to the source width', async () => { + const { host, base } = makeHost([ + ['u8', 0xF0, 'uint8'], + ['u16', 0x8001, 'uint16'], + ]); + + // u8 promoted for math but result truncated back to 8 bits + await expect(evalParsedNormalized('u8 << 4', host, base)).resolves.toBe(0); // 0xF0 << 4 = 0xF00 -> 0x00 in 8-bit + await expect(evalParsedNormalized('u8 >> 4', host, base)).resolves.toBe(0x0F); + + // u16 retains 16-bit wrap + await expect(evalParsedNormalized('u16 << 1', host, base)).resolves.toBe(0x0002); // 0x8001 << 1 => 0x0002 when truncated to 16 bits + await expect(evalParsedNormalized('u16 >> 1', host, base)).resolves.toBe(0x4000); + }); + + it('rejects JS-style unsigned shift', async () => { + const { host, base } = makeHost([ + ['u32', 1, 'uint32'], + ]); + await expect(evalParsedNormalized('u32 >>> 1', host, base)).resolves.toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-const-eval.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-const-eval.test.ts new file mode 100644 index 00000000..cc012cdf --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-const-eval.test.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ParserConstEval. + */ + +import { BinaryExpression, Identifier, NumberLiteral, parseExpression } from '../../../../parser-evaluator/parser'; + +interface Case { expr: string; expected: number | boolean | string; } +interface NonConstCase { + expr: string; + symbols?: string[]; + foldedTo?: { left: string; right: number }; +} + +interface ParserCasesFile { + constCases: Case[]; + nonConstCases: NonConstCase[]; +} + +// eslint-disable-next-line @typescript-eslint/no-require-imports -- static test fixture load +const cases: ParserCasesFile = require('../../testfiles/cases.json'); + +describe('Parser constant folding', () => { + const { constCases, nonConstCases } = cases; + + it('produces constValue for folded expressions', () => { + for (const { expr, expected } of constCases) { + const pr = parseExpression(expr, false); + expect(pr.diagnostics).toEqual([]); + expect(pr.constValue).toBe(expected); + } + }); + + it('keeps constValue undefined for expressions with symbols', () => { + for (const { expr, symbols } of nonConstCases) { + const pr = parseExpression(expr, false); + expect(pr.diagnostics).toEqual([]); + expect(pr.constValue).toBeUndefined(); + if (symbols && symbols.length) { + for (const sym of symbols) { + expect(pr.externalSymbols).toContain(sym); + } + } + const expected = (nonConstCases.find(c => c.expr === expr) as NonConstCase).foldedTo; + if (expected) { + expect(pr.ast.kind).toBe('BinaryExpression'); + const ast = pr.ast as BinaryExpression; + expect(ast.operator).toBe('+'); + expect(ast.left.kind).toBe('Identifier'); + expect((ast.left as Identifier).name).toBe(expected.left); + expect(ast.right.kind).toBe('NumberLiteral'); + expect((ast.right as NumberLiteral).value).toBe(expected.right); + } + } + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-coverage.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-coverage.test.ts new file mode 100644 index 00000000..8ea550e0 --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-coverage.test.ts @@ -0,0 +1,453 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ParserCoverage. + */ + +import { + type ASTNode, + type BinaryExpression, + type UnaryExpression, + type Diagnostic, + type EvalPointCall, + type FormatSegment, + type ConstValue, + type NumberLiteral, + type PrintfExpression, + type TextSegment, + type UpdateExpression, + type ErrorNode, + type Identifier, + Parser, + __parserTestUtils, + parseExpression +} from '../../../../parser-evaluator/parser'; + +type ParserPrivate = { + diagnostics: Diagnostic[]; + reset(input: string): void; + eat(token: string): void; + fold(node: ASTNode): ASTNode; + parse: (...args: unknown[]) => ASTNode; + parseWithDiagnostics(input: string, allowPrintf: boolean): { ast: ASTNode; diagnostics: Diagnostic[] }; +}; + +function asPrintf(ast: ASTNode): PrintfExpression { + if (ast.kind !== 'PrintfExpression') { + throw new Error(`Expected PrintfExpression, got ${ast.kind}`); + } + return ast; +} + +function findFormat(segments: Array): FormatSegment | undefined { + return segments.find((s): s is FormatSegment => s.kind === 'FormatSegment'); +} + +describe('parser', () => { + it('auto-detects printf expressions and parses segments', () => { + const pr = parseExpression('val=%x[sym]', false); + expect(pr.isPrintf).toBe(true); + expect(pr.diagnostics).toHaveLength(0); + expect(pr.ast.kind).toBe('PrintfExpression'); + const segments = asPrintf(pr.ast).segments; + expect(Array.isArray(segments)).toBe(true); + expect(findFormat(segments)?.spec).toBe('x'); + }); + + it('parses eval-point intrinsic calls', () => { + const pr = parseExpression('__GetRegVal(r0)', false); + expect(pr.diagnostics).toHaveLength(0); + expect(pr.ast.kind).toBe('EvalPointCall'); + expect((pr.ast as EvalPointCall).intrinsic).toBe('__GetRegVal'); + }); + + it('parses colon paths', () => { + const pr = parseExpression('Type:field:EnumVal', false); + expect(pr.diagnostics).toHaveLength(0); + expect(pr.ast.kind).toBe('ColonPath'); + expect((pr.ast as { kind: 'ColonPath'; parts: string[] }).parts).toEqual(['Type', 'field', 'EnumVal']); + }); + + it('parses update expressions (postfix)', () => { + const pr = parseExpression('foo++', false); + expect(pr.diagnostics).toHaveLength(0); + expect(pr.ast.kind).toBe('UpdateExpression'); + const node = pr.ast as UpdateExpression; + expect(node.operator).toBe('++'); + expect(node.prefix).toBe(false); + }); + + it('reports diagnostics on unterminated printf bracket', () => { + const pr = parseExpression('%x[foo', true); + expect(pr.isPrintf).toBe(true); + expect(pr.diagnostics.length).toBeGreaterThan(0); + }); + + it('tracks external symbols and drops assigned identifiers', () => { + const pr = parseExpression('a = b', false); + expect(pr.diagnostics).toHaveLength(0); + expect(pr.externalSymbols).toEqual(['b']); + }); + + it('warns on trailing tokens', () => { + const pr = parseExpression('1 2', false); + expect(pr.diagnostics.length).toBeGreaterThan(0); + expect(pr.diagnostics.some(d => d.type === 'warning')).toBe(true); + }); + + it('parses octal, binary, and exponent numbers', () => { + expect(parseExpression('0o17', false).ast.constValue).toBe(15); + expect(parseExpression('0b1011', false).ast.constValue).toBe(11); + expect(parseExpression('1.5e2', false).ast.constValue).toBe(150); + }); + + it('unescapes valid and invalid string escapes', () => { + expect(parseExpression('"\\u0041"', false).ast.constValue).toBe('A'); + expect(parseExpression('"\\u{1F600}"', false).ast.constValue).toBe('😀'); + expect(parseExpression('"\\xZZ"', false).ast.constValue).toBe('xZZ'); + expect(parseExpression('"unterminated\\\\\\"', false).ast.constValue).toBe('unterminated\\\\'); + }); + + it('covers tokenizer branches (exponent signs and unknown tokens)', () => { + expect(parseExpression('1e-3', false).ast.constValue).toBeCloseTo(0.001); + const unknown = parseExpression('@', false); + expect(unknown.diagnostics.some(d => d.type === 'error')).toBe(true); + }); + + it('folds to string and boolean literals when possible', () => { + expect(parseExpression('"a" + "b"', false).ast.constValue).toBe('ab'); + expect(parseExpression('1 == 1', false).ast.constValue).toBe(true); + expect(parseExpression('\'\\\'\'', false).ast.constValue).toBe(39); + }); + + it('handles additional invalid escape sequences', () => { + expect(parseExpression('"\\u{ZZ}"', false).ast.constValue).toBe('u{ZZ}'); + expect(parseExpression('"\\u00GZ"', false).ast.constValue).toBe('u00GZ'); + expect(parseExpression('"\\x41"', false).ast.constValue).toBe('A'); + }); + + it('covers all simple escape sequences and default escape handling', () => { + // eslint-disable-next-line quotes, no-useless-escape + const val = parseExpression(`"\\n\\r\\t\\b\\f\\v\\\\\\\"'\\0\\q"`, false).ast.constValue as string; + expect(val).toBe('\n\r\t\b\f\v\\"\'\0q'); + }); + + it('handles NaN from malformed numeric literals', () => { + const res = parseExpression('0x', false); + const ast = res.ast as NumberLiteral; + expect(Number.isNaN(ast.value)).toBe(true); + }); + + it('covers printf edge cases and scanning logic', () => { + const empty = parseExpression('', true); + expect(empty.isPrintf).toBe(true); + expect(asPrintf(empty.ast).segments).toHaveLength(0); + + const trailingPercent = parseExpression('trail %', true); + const trailingSegments = asPrintf(trailingPercent.ast).segments; + const trailing = trailingSegments.at(-1); + expect(trailing && trailing.kind === 'TextSegment' ? trailing.text : undefined).toBe('%'); + + const noBracket = parseExpression('%x value', true); + const first = asPrintf(noBracket.ast).segments[0]; + expect(first.kind === 'TextSegment' ? first.text : undefined).toBe('%x'); + + const escapedString = parseExpression('%x["unterminated', true); + expect(escapedString.diagnostics.some(d => d.message.includes('Unclosed formatter bracket'))).toBe(true); + + const escapedWithin = parseExpression('%x["a\\\\\\"b"]', true); + expect(escapedWithin.diagnostics).toHaveLength(0); + const seg = asPrintf(escapedWithin.ast).segments.find(s => s.kind === 'FormatSegment') as FormatSegment | undefined; + expect(seg?.spec).toBe('x'); + + const forcedByDoublePercent = parseExpression('%% literal', false); + expect(forcedByDoublePercent.isPrintf).toBe(true); + + const semicolonInner = parseExpression('%x[1;]', true); + expect(asPrintf(semicolonInner.ast).segments.length).toBeGreaterThan(0); + }); + + it('parses plain printf text without specifiers', () => { + const pr = parseExpression('plain text', true); + expect(asPrintf(pr.ast).segments).toHaveLength(1); + }); + + it('reports malformed conditionals and invalid assignment targets', () => { + const missingColon = parseExpression('a ? b', false); + expect(missingColon.diagnostics.some(d => d.message.includes('Expected ":"'))).toBe(true); + + const badTarget = parseExpression('(a+b)=3', false); + expect(badTarget.diagnostics.some(d => d.message.includes('Invalid assignment target'))).toBe(true); + + const tooFewArgs = parseExpression('__CalcMemUsed(1)', false); + expect(tooFewArgs.diagnostics.some(d => d.message.includes('expects at least 4 argument'))).toBe(true); + const tooManyArgs = parseExpression('__GetRegVal(r0, r1)', false); + expect(tooManyArgs.diagnostics.some(d => d.message.includes('expects at most 1 argument'))).toBe(true); + }); + + it('parses prefix updates and colon-path failures', () => { + const prefix = parseExpression('++foo', false); + expect(prefix.ast.kind).toBe('UpdateExpression'); + expect((prefix.ast as UpdateExpression).prefix).toBe(true); + + const colonError = parseExpression('Type:', false); + expect(colonError.diagnostics.some(d => d.message.includes('Expected identifier after ":"'))).toBe(true); + + const colonPathContinuation = parseExpression('A:B::C', false); + expect(colonPathContinuation.diagnostics.some(d => d.message.includes('Expected identifier after ":"'))).toBe(true); + }); + + it('covers call/property/index errors and postfix validation', () => { + const call = parseExpression('fn(1', false); + expect(call.diagnostics.some(d => d.message.includes('Expected ")"'))).toBe(true); + + const prop = parseExpression('obj.', false); + expect(prop.diagnostics.some(d => d.message.includes('Expected identifier after "."'))).toBe(true); + + const idx = parseExpression('arr[1', false); + expect(idx.diagnostics.some(d => d.message.includes('Expected "]"'))).toBe(true); + + const postfix = parseExpression('(1+2)++', false); + expect(postfix.diagnostics.some(d => d.message.includes('Invalid increment/decrement target'))).toBe(true); + + const prefixInvalid = parseExpression('++(1+2)', false); + expect(prefixInvalid.diagnostics.some(d => d.message.includes('Invalid increment/decrement target'))).toBe(true); + }); + + it('folds unary plus, bitwise not, and addition chains', () => { + expect(parseExpression('+5', false).ast.constValue).toBe(5); + expect(parseExpression('~1', false).ast.constValue).toBe(4294967294); + + const chain = parseExpression('foo + 1 + 2', false); + const chainAst = chain.ast as BinaryExpression; + expect((chainAst.right as NumberLiteral).value).toBe(3); + + const nonCombine = parseExpression('(foo-1)+2', false).ast as BinaryExpression; + expect((nonCombine.left as BinaryExpression).operator).toBe('-'); + }); + + it('folds printf segments and nested expressions', () => { + const pr = parseExpression('v=%x[1+2]', true); + expect(pr.ast.kind).toBe('PrintfExpression'); + const seg = findFormat(asPrintf(pr.ast).segments); + expect(seg?.value.constValue).toBe(3); + }); + + it('folds additional binary operators and detects div by zero', () => { + expect(parseExpression('5 % 2', false).ast.constValue).toBe(1); + expect(parseExpression('5-2', false).ast.constValue).toBe(3); + expect(parseExpression('4/2', false).ast.constValue).toBe(2); + expect(parseExpression('1 != 2', false).ast.constValue).toBe(true); + expect(parseExpression('1 < 2', false).ast.constValue).toBe(true); + expect(parseExpression('2 <= 2', false).ast.constValue).toBe(true); + expect(parseExpression('3 > 2', false).ast.constValue).toBe(true); + expect(parseExpression('3 >= 3', false).ast.constValue).toBe(true); + expect(parseExpression('1 && 0', false).ast.constValue).toBe(false); + expect(parseExpression('1 || 0', false).ast.constValue).toBe(true); + expect(parseExpression('0 || 1', false).ast.constValue).toBe(true); + const divZero = parseExpression('1/0', false); + expect(divZero.diagnostics.some(d => d.message.includes('Division by zero'))).toBe(true); + }); + + it('records diagnostics when eat() sees unexpected tokens', () => { + const parser = new Parser() as unknown as ParserPrivate; + parser.reset(''); + parser.diagnostics = []; + parser.eat('IDENT'); + expect(parser.diagnostics.some((d: Diagnostic) => d.message.includes('Expected IDENT'))).toBe(true); + }); + + it('covers fold error paths via direct invocation', () => { + const parser = new Parser() as unknown as ParserPrivate; + const throwingPrimitive = { valueOf: () => { throw 'boom'; }, toString: () => { throw 'boom'; } }; + + const badUnaryArg: ErrorNode = { kind: 'ErrorNode', message: 'boom', constValue: throwingPrimitive as unknown as ConstValue, start: 0, end: 1 }; + const unaryNode: UnaryExpression = { kind: 'UnaryExpression', operator: '+', argument: badUnaryArg, start: 0, end: 1 }; + const unaryResult = parser.fold(unaryNode); + expect(parser.diagnostics.some((d: Diagnostic) => d.message.includes('Failed to fold unary expression'))).toBe(true); + expect(unaryResult.constValue).toBeUndefined(); + + const oddUnary: UnaryExpression = { kind: 'UnaryExpression', operator: '*' as '+' | '-' | '!' | '~', argument: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 1 }, start: 0, end: 1 }; + const oddUnaryResult = parser.fold(oddUnary); + expect(oddUnaryResult.constValue).toBeUndefined(); + + const badBinaryLeft: ErrorNode = { kind: 'ErrorNode', message: 'bin', constValue: throwingPrimitive as unknown as ConstValue, start: 0, end: 1 }; + const badBinaryRight: NumberLiteral = { kind: 'NumberLiteral', value: 2, raw: '2', valueType: 'number', constValue: 2, start: 0, end: 1 }; + const badBinary: BinaryExpression = { kind: 'BinaryExpression', operator: '+', left: badBinaryLeft, right: badBinaryRight, start: 0, end: 1 }; + const badBinaryResult = parser.fold(badBinary); + expect(parser.diagnostics.some((d: Diagnostic) => d.message.includes('Failed to fold binary expression'))).toBe(true); + expect(badBinaryResult.constValue).toBeUndefined(); + + const errId: Identifier = { kind: 'Identifier', name: 'x', constValue: { valueOf: () => { throw new Error('err'); } } as unknown as ConstValue, valueType: 'unknown', start: 0, end: 1 }; + const errUnary: UnaryExpression = { kind: 'UnaryExpression', operator: '+', argument: errId, start: 0, end: 1 }; + parser.fold(errUnary); + + const literalFallback = __parserTestUtils.literalFromConst(undefined, 0, 1); + expect(literalFallback.kind).toBe('ErrorNode'); + + const errBinary: BinaryExpression = { kind: 'BinaryExpression', operator: '+', left: errId, right: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 0, end: 1 }, start: 0, end: 1 }; + parser.fold(errBinary); + + // BigInt normalization, modulo-by-zero early return, and unknown operator fallbacks + const bigLeft: NumberLiteral = { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1n as unknown as ConstValue, start: 0, end: 1 }; + const bigRight: NumberLiteral = { kind: 'NumberLiteral', value: 2, raw: '2', valueType: 'number', constValue: 2n as unknown as ConstValue, start: 0, end: 1 }; + const bigintSum = parser.fold({ kind: 'BinaryExpression', operator: '+', left: bigLeft, right: bigRight, start: 0, end: 1 }); + expect(bigintSum.constValue).toBe(3); // bigint coerced to number + + const modZeroParsed = parseExpression('1%0', false); + expect(modZeroParsed.ast.constValue).toBeUndefined(); + + const unknownOp = parser.fold({ kind: 'BinaryExpression', operator: '**', left: bigLeft, right: bigRight, start: 0, end: 1 }); + expect(unknownOp.constValue).toBeUndefined(); + }); + + it('captures exceptions via parseWithDiagnostics', () => { + const parser = new Parser() as unknown as ParserPrivate; + parser.parse = () => { throw new Error('boom'); }; + const res = parser.parseWithDiagnostics('x', false); + expect(res.ast.kind).toBe('ErrorNode'); + expect(res.diagnostics.some((d: Diagnostic) => d.message.includes('boom'))).toBe(true); + }); + + it('handles AggregateError branches and fallback messages', () => { + const parser = new Parser() as unknown as ParserPrivate; + parser.parse = () => { throw new AggregateError(['str'], 'agg'); }; + const res = parser.parseWithDiagnostics('x', false); + expect(res.diagnostics.some((d: Diagnostic) => d.message.includes('str'))).toBe(true); + + parser.parse = () => { throw new AggregateError([], 'empty'); }; + const res2 = parser.parseWithDiagnostics('x', false); + expect((res2.ast as ErrorNode).message).toBe('Unknown parser error'); + }); + + it('covers map precedence fallback and non-identifier callees', () => { + const parserCtor = Parser as unknown as { PREC: Map }; + const prev = parserCtor.PREC.get('&&'); + parserCtor.PREC.set('&&', undefined); + expect(parseExpression('a && b', false).ast.kind).toBe('Identifier'); + parserCtor.PREC.set('&&', prev); + + const call = parseExpression('(obj.fn)()', false); + expect((call.ast as { callee: ASTNode }).callee.kind).toBeDefined(); + }); + + it('covers empty char literal codepoint fallback', () => { + const res = parseExpression('\'\'', false); + expect(res.ast.constValue).toBe(0); + }); + + it('covers boolean literals, hex scanning, and grouped expression diagnostics', () => { + expect(parseExpression('true', false).ast.constValue).toBe(true); + expect(parseExpression('false', false).ast.constValue).toBe(false); + expect(parseExpression('0x1f', false).ast.constValue).toBe(31); + const missingParen = parseExpression('(1', false); + expect(missingParen.diagnostics.some(d => d.message.includes('Expected ")"'))).toBe(true); + }); + + it('folds member/array access operands without altering structure', () => { + const parser = new Parser() as unknown as ParserPrivate; + const member = parser.fold({ + kind: 'MemberAccess', + object: { kind: 'Identifier', name: 'foo', valueType: 'unknown', start: 0, end: 3 }, + property: { kind: 'Identifier', name: 'bar', valueType: 'unknown', start: 4, end: 7 }, + start: 0, + end: 7 + } as unknown as ASTNode); + expect(member.kind).toBe('MemberAccess'); + + const arrayIdx = parser.fold({ + kind: 'ArrayIndex', + array: { kind: 'Identifier', name: 'arr', valueType: 'unknown', start: 0, end: 3 }, + index: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1, start: 4, end: 5 }, + start: 0, + end: 5 + } as unknown as ASTNode); + expect(arrayIdx.kind).toBe('ArrayIndex'); + }); + + it('folds unary/logical/conditional expressions and BigInt coercion', () => { + expect(parseExpression('!1', false).ast.constValue).toBe(false); + expect(parseExpression('~0', false).ast.constValue).toBe(4294967295); + expect(parseExpression('0 && foo', false).ast.constValue).toBe(false); + expect(parseExpression('1 || foo', false).ast.constValue).toBe(true); + expect(parseExpression('true ? 1 : 2', false).ast.constValue).toBe(1); + + const parser = new Parser() as unknown as ParserPrivate; + const bigLeft = { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1n as unknown as ConstValue, start: 0, end: 1 } as NumberLiteral; + const bigRight = { kind: 'NumberLiteral', value: 2, raw: '2', valueType: 'number', constValue: 2n as unknown as ConstValue, start: 0, end: 1 } as NumberLiteral; + const folded = parser.fold({ kind: 'BinaryExpression', operator: '+', left: bigLeft, right: bigRight, start: 0, end: 1 }); + expect(folded.constValue).toBe(3); + + expect(parseExpression('-2', false).ast.constValue).toBe(-2); + expect(parseExpression('+foo', false).ast.constValue).toBeUndefined(); + const falseTernary = parseExpression('false ? 1 : 2', false); + expect(falseTernary.ast.constValue).toBe(2); + + const bigintNot = parser.fold({ + kind: 'UnaryExpression', + operator: '~', + argument: { kind: 'NumberLiteral', value: 1, raw: '1', valueType: 'number', constValue: 1n as unknown as ConstValue, start: 0, end: 1 }, + start: 0, + end: 1 + } as unknown as ASTNode); + expect(typeof bigintNot.constValue).toBe('number'); + + // Exercise bigint branches in foldBinaryConst (div/mod zero and non-zero) + const bigZero: NumberLiteral = { kind: 'NumberLiteral', value: 0, raw: '0', valueType: 'number', constValue: 0n as unknown as ConstValue, start: 0, end: 1 }; + const divZeroBig = parser.fold({ kind: 'BinaryExpression', operator: '/', left: bigLeft, right: bigZero, start: 0, end: 1 }); + expect((divZeroBig as { constValue?: ConstValue }).constValue).toBeUndefined(); + const modBig = parser.fold({ kind: 'BinaryExpression', operator: '%', left: bigRight, right: bigLeft, start: 0, end: 1 }); + expect(modBig.constValue).toBe(0); + }); + it('folds remaining binary operators and normalizes const values', () => { + expect(parseExpression('5 * 2', false).ast.constValue).toBe(10); + expect(parseExpression('7 - 3', false).ast.constValue).toBe(4); + expect(parseExpression('1 << 3', false).ast.constValue).toBe(8); + expect(parseExpression('8 >> 1', false).ast.constValue).toBe(4); + expect(parseExpression('1 & 3', false).ast.constValue).toBe(1); + expect(parseExpression('1 ^ 3', false).ast.constValue).toBe(2); + expect(parseExpression('1 | 2', false).ast.constValue).toBe(3); + const bigNormalized = parseExpression('1 + 9007199254740993', false).ast.constValue; + expect(typeof bigNormalized).toBe('number'); + expect(bigNormalized as number).toBeGreaterThan(9e15); + const idxOk = parseExpression('arr[1]', false); + expect(idxOk.ast.kind).toBe('ArrayIndex'); + }); + + it('consumes trailing semicolons and leaves diagnostics for stray tokens', () => { + const trailingSemicolons = parseExpression('1;;;', false); + expect(trailingSemicolons.diagnostics).toHaveLength(0); + + const strayColon = parseExpression('1:2', false); + expect(strayColon.diagnostics.some(d => d.message.includes('Extra tokens'))).toBe(true); + }); + + it('handles nested formatter brackets in printf parsing', () => { + const nested = parseExpression('%x[[1]]', true); + expect(nested.isPrintf).toBe(true); + expect(asPrintf(nested.ast).segments).toHaveLength(1); + }); + + it('parses multiple call arguments', () => { + const call = parseExpression('fn(1,2,3)', false); + const callAst = call.ast as { kind: string; args?: unknown[] }; + expect(callAst.kind).toBe('CallExpression'); + expect(callAst.args && callAst.args.length).toBe(3); + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-intrinsics.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-intrinsics.test.ts new file mode 100644 index 00000000..7ba7bc8a --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-intrinsics.test.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ParserIntrinsics. + */ + +import { parseExpression, EvalPointCall, Identifier } from '../../../../parser-evaluator/parser'; +import { INTRINSIC_DEFINITIONS, type IntrinsicName } from '../../../../parser-evaluator/intrinsics'; + +type IntrinsicFixture = { + intrinsics: string[]; + pseudoMembers?: string[]; +}; + +// eslint-disable-next-line @typescript-eslint/no-require-imports -- static test fixture load +const fixture: IntrinsicFixture = require('../../testfiles/cases.json'); + +describe('Parser intrinsics', () => { + it('parses all intrinsic calls as EvalPointCall', () => { + for (const name of fixture.intrinsics) { + const meta = INTRINSIC_DEFINITIONS[name as IntrinsicName]; + const argCount = meta?.minArgs ?? 0; + const args = Array.from({ length: argCount }, (_, i) => i + 1).join(', '); + const pr = parseExpression(`${name}(${args})`, false); + expect(pr.diagnostics).toEqual([]); + expect(pr.isPrintf).toBe(false); + expect(pr.constValue).toBeUndefined(); + expect(pr.ast.kind).toBe('EvalPointCall'); + const call = pr.ast as EvalPointCall; + expect(call.intrinsic).toBe(name); + expect(call.callee.kind).toBe('Identifier'); + expect((call.callee as Identifier).name).toBe(name); + } + }); + + it('parses pseudo-member helpers (_count/_addr) as MemberAccess', () => { + const members = fixture.pseudoMembers ?? []; + for (const expr of members) { + const pr = parseExpression(expr, false); + expect(pr.diagnostics).toEqual([]); + expect(pr.constValue).toBeUndefined(); + expect(pr.ast.kind).toBe('MemberAccess'); + } + }); +}); diff --git a/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-scvd-expressions.test.ts b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-scvd-expressions.test.ts new file mode 100644 index 00000000..71cf6668 --- /dev/null +++ b/src/views/component-viewer/test/integration/parser-evaluator/parser/parser-scvd-expressions.test.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ParserScvdExpressions. + */ + +import { parseExpression, ParseResult } from '../../../../parser-evaluator/parser'; + +interface ExpressionRow { + expr: string; + isPrintf?: boolean; +} + +interface ExpressionFile { + _meta: { format: string; totalOriginal: number; totalUnique: number; sourceFiles: string[] }; + expressions: ExpressionRow[]; +} + +// eslint-disable-next-line @typescript-eslint/no-require-imports -- static test fixture load +const expressionFixture: ExpressionFile = require('../../testfiles/expressions.json'); + +function readExpressions(file: string): ExpressionFile { + const parsed = expressionFixture; + if (!Array.isArray(parsed.expressions)) { + throw new Error(`Expression file missing expressions array: ${file}`); + } + return parsed; +} + +function parseAll(rows: ExpressionRow[]): { parsed: ParseResult[]; diagnostics: number } { + let diagnostics = 0; + const parsed = rows.map((row, idx) => { + let pr: ParseResult; + try { + pr = parseExpression(row.expr, !!row.isPrintf); + } catch (err) { + throw new Error(`Parser threw for expression #${idx}: ${row.expr}\n${err instanceof Error ? err.stack ?? err.message : String(err)}`); + } + diagnostics += pr.diagnostics?.length ?? 0; + expect(pr).toBeTruthy(); + expect(pr.ast).toBeTruthy(); + expect(pr.isPrintf).toBe(row.isPrintf ?? false); + return pr; + }); + return { parsed, diagnostics }; +} + +describe('Parser over SCVD expression fixtures', () => { + it('parses every expression without throwing', () => { + const timeoutHint = setTimeout(() => { + // If this prints, the default Jest timeout is likely to be hit. + + console.warn('Parser SCVD expressions test is running long; consider increasing the test timeout or reducing fixture size.'); + }, 4500); + const { _meta, expressions } = readExpressions('expressions.json'); + try { + expect(expressions.length).toBe(_meta.totalUnique); + + const { diagnostics } = parseAll(expressions); + + // The parser should be tolerant; fail hard if diagnostics explode unexpectedly. + expect(diagnostics).toBeGreaterThanOrEqual(0); + } finally { + clearTimeout(timeoutHint); + } + }); +}); diff --git a/src/views/component-viewer/test/integration/scvd-debug-target.test.ts b/src/views/component-viewer/test/integration/scvd-debug-target.test.ts new file mode 100644 index 00000000..4214406f --- /dev/null +++ b/src/views/component-viewer/test/integration/scvd-debug-target.test.ts @@ -0,0 +1,261 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for ScvdDebugTarget. + */ + +import { ScvdDebugTarget, gdbNameFor, __test__ } from '../../scvd-debug-target'; +import type { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../../../debug-session'; + +type AccessMock = { + setActiveSession: jest.Mock; + evaluateSymbolAddress: jest.Mock; + evaluateSymbolName: jest.Mock; + evaluateSymbolContext: jest.Mock; + evaluateNumberOfArrayElements: jest.Mock; + evaluateSymbolSize: jest.Mock; + evaluateMemory: jest.Mock; + evaluateRegisterValue: jest.Mock; +}; + +let accessMock: AccessMock; +jest.mock('../../component-viewer-target-access', () => ({ + ComponentViewerTargetAccess: jest.fn(() => accessMock), +})); + +const session = { session: { id: 'sess-1' } } as unknown as GDBTargetDebugSession; + +describe('scvd-debug-target', () => { + beforeEach(() => { + accessMock = { + setActiveSession: jest.fn(), + evaluateSymbolAddress: jest.fn(), + evaluateSymbolName: jest.fn(), + evaluateSymbolContext: jest.fn(), + evaluateNumberOfArrayElements: jest.fn(), + evaluateSymbolSize: jest.fn(), + evaluateMemory: jest.fn(), + evaluateRegisterValue: jest.fn(), + }; + jest.clearAllMocks(); + }); + + it('normalizes register names and maps to gdb names', () => { + expect(gdbNameFor(' r0 ')).toBe('r0'); + expect(gdbNameFor('MSP_s')).toBe('msp_s'); + expect(gdbNameFor('unknown')).toBeUndefined(); + }); + + it('resolves symbol info when session is active', async () => { + accessMock.evaluateSymbolAddress.mockResolvedValue('0x100'); + const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker; + const target = new ScvdDebugTarget(); + target.init(session, tracker); + + await expect(target.getSymbolInfo('foo')).resolves.toEqual({ name: 'foo', address: 0x100 }); + + accessMock.evaluateSymbolAddress.mockResolvedValue('zzz'); + const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(target.getSymbolInfo('foo')).resolves.toBeUndefined(); + spy.mockRestore(); + }); + + it('returns undefined for missing session or symbol', async () => { + const target = new ScvdDebugTarget(); + await expect(target.getSymbolInfo(undefined as unknown as string)).resolves.toBeUndefined(); + await expect(target.getSymbolInfo('foo')).resolves.toBeUndefined(); + await expect(target.findSymbolNameAtAddress(0x200)).resolves.toBeUndefined(); + await expect(target.findSymbolContextAtAddress(0x200n)).resolves.toBeUndefined(); + await expect(target.getNumArrayElements('arr')).resolves.toBeUndefined(); + await expect(target.getNumArrayElements(undefined as unknown as string)).resolves.toBeUndefined(); + await expect(target.getTargetIsRunning()).resolves.toBe(false); + await expect(target.getSymbolSize('sym')).resolves.toBeUndefined(); + await expect(target.readMemory(0, 4)).resolves.toBeUndefined(); + }); + + it('finds symbol name and context, handling errors', async () => { + accessMock.evaluateSymbolName.mockResolvedValue('main'); + accessMock.evaluateSymbolContext.mockResolvedValue('file.c:10'); + const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker; + const target = new ScvdDebugTarget(); + target.init(session, tracker); + + await expect(target.findSymbolNameAtAddress(0x200)).resolves.toBe('main'); + await expect(target.findSymbolContextAtAddress(0x200)).resolves.toBe('file.c:10'); + + const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); + accessMock.evaluateSymbolName.mockRejectedValue(new Error('fail')); + await expect(target.findSymbolNameAtAddress(0x200)).resolves.toBeUndefined(); + accessMock.evaluateSymbolContext.mockRejectedValue(new Error('fail')); + await expect(target.findSymbolContextAtAddress(0x200)).resolves.toBeUndefined(); + spy.mockRestore(); + }); + + it('handles array length and running state tracking', async () => { + type TrackerWithCallbacks = { + onContinued: (cb: (event: { session: GDBTargetDebugSession }) => void) => void; + onStopped: (cb: (event: { session: GDBTargetDebugSession }) => void) => void; + _continued?: (event: { session: GDBTargetDebugSession }) => void; + _stopped?: (event: { session: GDBTargetDebugSession }) => void; + }; + const tracker: TrackerWithCallbacks = { + onContinued: (cb) => { tracker._continued = cb; }, + onStopped: (cb) => { tracker._stopped = cb; }, + }; + + const target = new ScvdDebugTarget(); + target.init(session, tracker as unknown as GDBTargetDebugTracker); + + expect(await target.getNumArrayElements('sym')).toBeUndefined(); + accessMock.evaluateNumberOfArrayElements.mockResolvedValue(7); + await expect(target.getNumArrayElements('sym')).resolves.toBe(7); + + expect(await target.getTargetIsRunning()).toBe(false); + await tracker._continued?.({ session }); + expect(await target.getTargetIsRunning()).toBe(true); + await tracker._stopped?.({ session }); + expect(await target.getTargetIsRunning()).toBe(false); + + // Mismatched session id should be ignored + await tracker._continued?.({ session: { session: { id: 'other' } } as unknown as GDBTargetDebugSession }); + expect(await target.getTargetIsRunning()).toBe(false); + await tracker._stopped?.({ session: { session: { id: 'other' } } as unknown as GDBTargetDebugSession }); + expect(await target.getTargetIsRunning()).toBe(false); + }); + + it('finds symbol address and size', async () => { + accessMock.evaluateSymbolAddress.mockResolvedValue('0x200'); + accessMock.evaluateSymbolSize.mockResolvedValue(16); + const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker; + const target = new ScvdDebugTarget(); + target.init(session, tracker); + + await expect(target.findSymbolAddress('foo')).resolves.toBe(0x200); + await expect(target.getSymbolSize('foo')).resolves.toBe(16); + + accessMock.evaluateSymbolSize.mockResolvedValue(-1); + await expect(target.getSymbolSize('foo')).resolves.toBeUndefined(); + await expect(target.getSymbolSize('')).resolves.toBeUndefined(); + + accessMock.evaluateSymbolAddress.mockResolvedValue(undefined); + await expect(target.findSymbolAddress('foo')).resolves.toBeUndefined(); + }); + + it('decodes base64 and reads memory', async () => { + const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker; + const target = new ScvdDebugTarget(); + target.init(session, tracker); + + expect(target.decodeGdbData('AQID')).toEqual(new Uint8Array([1, 2, 3])); + expect(target.decodeGdbData('AQIDBA')).toEqual(new Uint8Array([1, 2, 3, 4])); + // atob path + const globalWithBuffer = global as unknown as { Buffer: typeof Buffer | undefined; atob: ((value: string) => string) | undefined }; + const origBuffer = globalWithBuffer.Buffer; + const origAtob = globalWithBuffer.atob; + globalWithBuffer.Buffer = undefined; + globalWithBuffer.atob = (str: string) => origBuffer?.from(str, 'base64').toString('binary') ?? ''; + expect(target.decodeGdbData('AQID')).toEqual(new Uint8Array([1, 2, 3])); + globalWithBuffer.Buffer = origBuffer; + globalWithBuffer.atob = origAtob; + + accessMock.evaluateMemory.mockResolvedValue('AQID'); + await expect(target.readMemory(0x0, 3)).resolves.toEqual(new Uint8Array([1, 2, 3])); + + accessMock.evaluateMemory.mockResolvedValue('Unable to read'); + await expect(target.readMemory(0x0, 3)).resolves.toBeUndefined(); + + accessMock.evaluateMemory.mockResolvedValue(undefined); + await expect(target.readMemory(0x0, 3)).resolves.toBeUndefined(); + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + accessMock.evaluateMemory.mockResolvedValue('No active session'); + await expect(target.readMemory(0x0, 3)).resolves.toBeUndefined(); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + + accessMock.evaluateMemory.mockResolvedValue('AQID'); // len 3 vs requested 4 + await expect(target.readMemory(0x0, 4)).resolves.toBeUndefined(); + }); + + it('calculates memory usage and overflow bit', async () => { + class MemTarget extends ScvdDebugTarget { + constructor(private readonly data: Uint8Array) { super(); } + async readMemory(): Promise { + return this.data; + } + } + // Two chunks: first fill pattern, second magic value triggers overflow bit + const data = new Uint8Array([0, 0, 0, 0, 0x44, 0x33, 0x22, 0x11]); + const target = new MemTarget(data); + const result = await target.calculateMemoryUsage(0, 8, 0, 0x11223344); + expect(result).toBeDefined(); + expect((result as number) >>> 0).toBe(0x80000000); + + // No data path + const noData = new MemTarget(undefined as unknown as Uint8Array); + await expect(noData.calculateMemoryUsage(0, 4, 0, 0)).resolves.toBeUndefined(); + + const used = new MemTarget(new Uint8Array([1, 0, 0, 0, 1, 0, 0, 0])); + const usedResult = await used.calculateMemoryUsage(0, 8, 0, 0); + expect((usedResult as number) >>> 0).toBeGreaterThan(0); + expect(((usedResult as number) >>> 31) & 1).toBe(0); + }); + + it('reads string from pointer and registers', async () => { + const target = new ScvdDebugTarget(); + await expect(target.readUint8ArrayStrFromPointer(0, 1, 4)).resolves.toBeUndefined(); + + const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker; + target.init(session, tracker); + target.readMemory = jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])); + await expect(target.readUint8ArrayStrFromPointer(1, 1, 4)).resolves.toEqual(new Uint8Array([1, 2, 3, 4])); + + const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(target.readRegister('unknown')).resolves.toBeUndefined(); + spy.mockRestore(); + + accessMock.evaluateRegisterValue.mockResolvedValue(5); + await expect(target.readRegister('r0')).resolves.toBe(5); + + accessMock.evaluateRegisterValue.mockResolvedValue(undefined); + await expect(target.readRegister('r0')).resolves.toBeUndefined(); + + // Bigint toUint32 helper + expect(__test__.toUint32(0x1_0000_0000n)).toBe(0n); + + await expect(target.readRegister(undefined as unknown as string)).resolves.toBeUndefined(); + }); + + it('throws when no base64 decoder is available', () => { + const target = new ScvdDebugTarget(); + const globalWithBuffer = global as unknown as { Buffer: typeof Buffer | undefined; atob: ((value: string) => string) | undefined }; + const origBuffer = globalWithBuffer.Buffer; + const origAtob = globalWithBuffer.atob; + // Remove decoders + globalWithBuffer.Buffer = undefined; + globalWithBuffer.atob = undefined; + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + expect(target.decodeGdbData('AQID')).toBeUndefined(); + expect(errorSpy).toHaveBeenCalledWith('ScvdDebugTarget.decodeGdbData: no base64 decoder available in this environment'); + errorSpy.mockRestore(); + // restore + globalWithBuffer.Buffer = origBuffer; + globalWithBuffer.atob = origAtob; + }); +}); diff --git a/src/views/component-viewer/test/integration/testfile-generation/generate-scvd-expressions.test.ts b/src/views/component-viewer/test/integration/testfile-generation/generate-scvd-expressions.test.ts new file mode 100644 index 00000000..10e573f5 --- /dev/null +++ b/src/views/component-viewer/test/integration/testfile-generation/generate-scvd-expressions.test.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for GenerateScvdExpressions. + */ + +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { + decodeEntities, + extractExpressionsFromScvd, + writeJsonl, +} from './generate-scvd-expressions'; + +describe('generate-scvd-expressions', () => { + it('decodes entities and extracts expressions with flags', () => { + const content = ` + + + a < b + + + `; + + const expressions = extractExpressionsFromScvd(content); + + expect(expressions).toEqual([ + { expr: '1 & 2', forcePrintf: false }, + { expr: '%d[%s]', forcePrintf: true }, + { expr: 'a < b', forcePrintf: false }, + ]); + + expect(decodeEntities('A > B & C')).toBe('A > B & C'); + }); + + it('writes JSONL with printf detection', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'scvd-expr-')); + const outPath = path.join(tempDir, 'out.jsonl'); + + writeJsonl(outPath, [ + { expr: '%%', forcePrintf: false }, + { expr: 'plain', forcePrintf: true }, + ]); + + // eslint-disable-next-line security/detect-non-literal-fs-filename + const lines = fs.readFileSync(outPath, 'utf8').trim().split('\n'); + const meta = JSON.parse(lines[0]!); + const first = JSON.parse(lines[1]!); + const second = JSON.parse(lines[2]!); + + expect(meta._meta.total).toBe(2); + expect(first.isPrintf).toBe(true); + expect(second.isPrintf).toBe(true); + }); + + it('main reads inputs and writes outputs', async () => { + const readFileSync = jest.fn(() => 'offset="1"'); + const writeFileSync = jest.fn(); + const mkdirSync = jest.fn(); + + await jest.isolateModulesAsync(async () => { + jest.doMock('fs', () => ({ + readFileSync, + writeFileSync, + mkdirSync, + })); + const mod = await import('./generate-scvd-expressions'); + + mod.main(); + }); + + expect(readFileSync).toHaveBeenCalledTimes(3); + expect(writeFileSync).toHaveBeenCalledTimes(3); + }); +}); diff --git a/src/views/component-viewer/test/integration/testfile-generation/generate-scvd-expressions.ts b/src/views/component-viewer/test/integration/testfile-generation/generate-scvd-expressions.ts new file mode 100644 index 00000000..b59ea8f6 --- /dev/null +++ b/src/views/component-viewer/test/integration/testfile-generation/generate-scvd-expressions.ts @@ -0,0 +1,127 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Integration test for GenerateScvdExpressions. + */ + +import fs from 'fs'; +import path from 'path'; + +type Attr = { name: string; forcePrintf?: boolean }; +type Extracted = { expr: string; forcePrintf: boolean }; + +const ATTRS: Attr[] = [ + { name: 'offset' }, + { name: 'value' }, + { name: 'size' }, + { name: 'cond' }, + { name: 'symbol' }, + { name: 'count' }, + { name: 'init' }, + { name: 'start' }, + { name: 'limit' }, + { name: 'next' }, + { name: 'property', forcePrintf: true }, // property strings are printf-like templates + { name: 'id' }, + { name: 'hname' }, + { name: 'handle' }, +]; + +const PRINTF_RE = /%[^\s%]\s*\[|%%/; +const ATTR_NAME_TO_FLAG = new Map(ATTRS.map(({ name, forcePrintf }) => [name, !!forcePrintf])); +const ATTR_SCAN_RE = /(\w+)\s*=\s*"([^"]*)"/gi; + +export function decodeEntities(s: string): string { + return s + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '\''); +} + +export function extractExpressionsFromScvd(content: string): Extracted[] { + const expressions: Extracted[] = []; + + let m: RegExpExecArray | null; + while ((m = ATTR_SCAN_RE.exec(content)) !== null) { + const name = m[1]; + const forcePrintf = ATTR_NAME_TO_FLAG.get(name); + if (!ATTR_NAME_TO_FLAG.has(name)) { + continue; + } + const expr = decodeEntities(m[2]!.trim()); + if (expr) { + expressions.push({ expr, forcePrintf: !!forcePrintf }); + } + } + + const calcRe = /]*>([\s\S]*?)<\/calc>/gi; + let c: RegExpExecArray | null; + while ((c = calcRe.exec(content)) !== null) { + const inner = c[1]!; + inner + .split(/\r?\n/) + .map((line) => decodeEntities(line.trim())) + .filter(Boolean) + .forEach((expr) => expressions.push({ expr, forcePrintf: false })); + } + + return expressions; +} + +export function writeJsonl(outPath: string, expressions: Extracted[]): void { + // Paths are constructed from fixed repository locations; safe to create/read here. + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + const lines = [ + JSON.stringify({ _meta: { format: 'expressions-jsonl-v1', total: expressions.length } }), + ...expressions.map(({ expr, forcePrintf }, idx) => + JSON.stringify({ + i: idx + 1, + expr, + isPrintf: forcePrintf || PRINTF_RE.test(expr), + }), + ), + ]; + // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.writeFileSync(outPath, `${lines.join('\n')}\n`, 'utf8'); +} + +export function main(): void { + const root = path.join(__dirname, '../../../../..'); // repo root + const sources = ['RTX5', 'Network', 'USB'].map((base) => ({ + base, + file: path.join(root, 'src/component-viewer/test/test-files/scvd', `${base}.scvd`), + })); + + for (const { base, file } of sources) { + // eslint-disable-next-line security/detect-non-literal-fs-filename + const content = fs.readFileSync(file, 'utf8'); + const expressions = extractExpressionsFromScvd(content); + const out = path.join(root, 'src/views/component-viewer/test/integration/testfiles', `${base}_expressions.jsonl`); + writeJsonl(out, expressions); + + console.log(`Wrote ${expressions.length} expressions to ${out}`); + } +} + +if (require.main === module) { + main(); +} diff --git a/src/views/component-viewer/test/integration/testfiles/MinimalTest.scvd b/src/views/component-viewer/test/integration/testfiles/MinimalTest.scvd new file mode 100644 index 00000000..27b5864a --- /dev/null +++ b/src/views/component-viewer/test/integration/testfiles/MinimalTest.scvd @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + myVar[0].m_var1 = 10; + myVar[0].m_var2 = 20; + myVar[0].m_var3 = 30; + myVar[0].sum = myVar[0].m_var1 + myVar[0].m_var2 + myVar[0].m_var3; + myVar[1].m_var1 = 100; + myVar[1].m_var2 = 200; + myVar[1].m_var3 = 300; + myVar[1].sum = myVar[1].m_var1 + myVar[1].m_var2 + myVar[1].m_var3; + + + + + + + + + + + + + + + + diff --git a/src/views/component-viewer/test/integration/testfiles/MyTest.scvd b/src/views/component-viewer/test/integration/testfiles/MyTest.scvd new file mode 100644 index 00000000..7629d52d --- /dev/null +++ b/src/views/component-viewer/test/integration/testfiles/MyTest.scvd @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RTX_En = 1; + + + + + + + + + + + + + + R0 = __GetRegVal ("R0"); + i = 0; + j = 1; + k = 10; + l = 20; + + + + + + listVal1++; + + + + + i = 99; + + + + + + listVal2++; + l--; + + + + + memberTest.m_var1 = 0x11111111; + memberTest.a = 0x12; + memberTest.b = 0x34; + memberTest.c = 0x56; + memberTest.d = 0x78; + memberTest.sum = memberTest.d + (memberTest.c << 8) + (memberTest.b << 16) + (memberTest.a << 24); + memberTest.A = 0xAA; + memberTest.B = 0xBB; + memberTest.C = 0xCC; + memberTest.D = 0xDD; + memberTest.m_var2 = 0x22222222; + + + + + + + StackStart = __FindSymbol ("tstack"); + StackSize = __size_of ("tstack") * 4; + StackUsage = __CalcMemUsed (StackStart, StackSize, 0x8A8A8A8A, 0xE25A2EA5); + + + + + + + + V_Major = os_Info.version / 10000000; + V_Minor = (os_Info.version / 10000) % 1000; + V_Patch = os_Info.version % 10000; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/views/component-viewer/test/integration/testfiles/cases.json b/src/views/component-viewer/test/integration/testfiles/cases.json new file mode 100644 index 00000000..b5534276 --- /dev/null +++ b/src/views/component-viewer/test/integration/testfiles/cases.json @@ -0,0 +1,163 @@ +{ + "_meta": { + "format": "parser-cases-v1", + "copyright": "Copyright 2026 Arm Limited", + "counts": { + "const": 23, + "nonConst": 6, + "intrinsics": 7, + "pseudoMembers": 2 + }, + "generatedWith": "AI" + }, + "constCases": [ + { + "expr": "1+2", + "expected": 3 + }, + { + "expr": "-1+2", + "expected": 1 + }, + { + "expr": "0x12 + 7", + "expected": 25 + }, + { + "expr": "(3+4)*2", + "expected": 14 + }, + { + "expr": "((1+2)<<3) + (4>>1)", + "expected": 26 + }, + { + "expr": "1<<8", + "expected": 256 + }, + { + "expr": "5 & 3", + "expected": 1 + }, + { + "expr": "5 | 2", + "expected": 7 + }, + { + "expr": "5 ^ 2", + "expected": 7 + }, + { + "expr": "-1 >> 1", + "expected": 4294967295 + }, + { + "expr": "0xFF >> 4", + "expected": 15 + }, + { + "expr": "0b1010 & 0b1100", + "expected": 8 + }, + { + "expr": "~0", + "expected": 4294967295 + }, + { + "expr": "!0", + "expected": true + }, + { + "expr": "!!1", + "expected": true + }, + { + "expr": "1 + 2 * 3 << 2", + "expected": 28 + }, + { + "expr": "1 ? 2 + 3 * 4 : 0", + "expected": 14 + }, + { + "expr": "true ? 5 : 6", + "expected": 5 + }, + { + "expr": "false ? 5 : 6", + "expected": 6 + }, + { + "expr": "0 && foo", + "expected": false + }, + { + "expr": "1 || foo", + "expected": true + }, + { + "expr": "'A' + 1", + "expected": 66 + } + ], + "nonConstCases": [ + { + "expr": "foo + 1", + "symbols": [ + "foo" + ] + }, + { + "expr": "foo + 1 + 2", + "symbols": [ + "foo" + ], + "foldedTo": { + "left": "foo", + "right": 3 + } + }, + { + "expr": "foo + (1 << 3)", + "symbols": [ + "foo" + ], + "foldedTo": { + "left": "foo", + "right": 8 + } + }, + { + "expr": "foo ? 1+2 : bar", + "symbols": [ + "foo", + "bar" + ] + }, + { + "expr": "foo && 0", + "symbols": [ + "foo" + ] + }, + { + "expr": "foo || (1 + 2)", + "symbols": [ + "foo" + ] + } + ], + "intrinsics": [ + "__CalcMemUsed", + "__FindSymbol", + "__GetRegVal", + "__Offset_of", + "__size_of", + "__Symbol_exists", + "__Running" + ], + "pseudoMembers": [ + "foo._count", + "bar._addr" + ] +} diff --git a/src/views/component-viewer/test/integration/testfiles/evaluator-basic.json b/src/views/component-viewer/test/integration/testfiles/evaluator-basic.json new file mode 100644 index 00000000..4d4383d5 --- /dev/null +++ b/src/views/component-viewer/test/integration/testfiles/evaluator-basic.json @@ -0,0 +1,226 @@ +{ + "_meta": { + "format": "evaluator-basic-v1", + "copyright": "Copyright 2026 Arm Limited", + "generatedWith": "AI" + }, + "cases": [ + { + "expr": "1+2", + "expected": 3 + }, + { + "expr": "foo+1", + "expected": 3, + "symbols": { + "foo": { + "value": 2 + } + } + }, + { + "expr": "foo.bar + 2", + "expected": 7, + "symbols": { + "foo": { + "members": { + "bar": { + "value": 5 + } + } + } + } + }, + { + "expr": "__Running()", + "expected": 1 + }, + { + "expr": "__GetRegVal(r0)", + "expected": 7 + }, + { + "expr": "__FindSymbol(symA)", + "expected": 4660 + }, + { + "expr": "__CalcMemUsed(1,2,3,0)", + "expected": 6 + }, + { + "expr": "__size_of(foo)", + "expected": 4 + }, + { + "expr": "__Symbol_exists(symA)", + "expected": 1 + }, + { + "expr": "__Symbol_exists(missing)", + "expected": 0 + }, + { + "expr": "__Offset_of(memberA)", + "expected": 12 + }, + { + "expr": "foo = 5", + "expected": 5, + "symbols": { + "foo": { + "value": 0 + } + }, + "checkSymbol": "foo", + "expectedSymbol": 5 + }, + { + "expr": "-foo", + "expected": -2, + "symbols": { + "foo": { + "value": 2 + } + } + }, + { + "expr": "foo && (bar = 5)", + "expected": 0, + "symbols": { + "foo": { + "value": 0 + }, + "bar": { + "value": 1 + } + }, + "checkSymbol": "bar", + "expectedSymbol": 1 + }, + { + "expr": "flag ? 1 : 2", + "expected": 1, + "symbols": { + "flag": { + "value": 3 + } + } + }, + { + "expr": "true", + "expected": 1 + }, + { + "expr": "false", + "expected": 0 + }, + { + "expr": "foo++", + "expected": 3, + "symbols": { + "foo": { + "value": 3 + } + }, + "checkSymbol": "foo", + "expectedSymbol": 4 + }, + { + "expr": "foo += 4", + "expected": 9, + "symbols": { + "foo": { + "value": 5 + } + }, + "checkSymbol": "foo", + "expectedSymbol": 9 + }, + { + "expr": "arr[2]", + "expected": 30, + "symbols": { + "arr": { + "elements": { + "0": { + "value": 10 + }, + "1": { + "value": 20 + }, + "2": { + "value": 30 + } + } + } + } + }, + { + "expr": "arr[1].val", + "expected": 7, + "symbols": { + "arr": { + "elements": { + "1": { + "members": { + "val": { + "value": 7 + } + } + } + } + } + } + }, + { + "expr": "arr._count", + "expected": 3, + "symbols": { + "arr": { + "elements": { + "0": { + "value": 1 + }, + "1": { + "value": 2 + }, + "2": { + "value": 3 + } + } + } + } + }, + { + "expr": "node._addr", + "expected": 4096, + "symbols": { + "node": { + "value": 1, + "addr": 4096 + } + } + }, + { + "expr": "foo || bar", + "expected": 2, + "symbols": { + "foo": { + "value": 0 + }, + "bar": { + "value": 2 + } + } + }, + { + "expr": "value=%u[foo]", + "expected": "value=2", + "symbols": { + "foo": { + "value": 2 + } + } + } + ] +} diff --git a/src/views/component-viewer/test/integration/testfiles/expressions.json b/src/views/component-viewer/test/integration/testfiles/expressions.json new file mode 100644 index 00000000..c4fe9fe9 --- /dev/null +++ b/src/views/component-viewer/test/integration/testfiles/expressions.json @@ -0,0 +1,15991 @@ +{ + "_meta": { + "format": "parser-scvd-expressions-v1", + "copyright": "Copyright 2026 Arm Limited", + "totalOriginal": 12645, + "totalUnique": 3995, + "generatedWith": "AI" + }, + "expressions": [ + { + "expr": "0", + "isPrintf": false + }, + { + "expr": "12+0", + "isPrintf": false + }, + { + "expr": "os_Config.isr_queue_data", + "isPrintf": false + }, + { + "expr": "cb_Sections.thread_cb_start", + "isPrintf": false + }, + { + "expr": "cb_Sections.timer_cb_start", + "isPrintf": false + }, + { + "expr": "cb_Sections.evflags_cb_start", + "isPrintf": false + }, + { + "expr": "cb_Sections.mutex_cb_start", + "isPrintf": false + }, + { + "expr": "cb_Sections.mempool_cb_start", + "isPrintf": false + }, + { + "expr": "cb_Sections.semaphore_cb_start", + "isPrintf": false + }, + { + "expr": "cb_Sections.msgqueue_cb_start", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_stack", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_thread", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_timer", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_event_flags", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_mutex", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_semaphore", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_memory_pool", + "isPrintf": false + }, + { + "expr": "os_Config.mpi_message_queue", + "isPrintf": false + }, + { + "expr": "os_Info.thread_idle", + "isPrintf": false + }, + { + "expr": "os_Info.timer_thread", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_thread", + "isPrintf": false + }, + { + "expr": "mp_thread.block_base", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_timer", + "isPrintf": false + }, + { + "expr": "mp_timer.block_base", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_event_flags", + "isPrintf": false + }, + { + "expr": "mp_events.block_base", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_mutex", + "isPrintf": false + }, + { + "expr": "mp_mutex.block_base", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_semaphore", + "isPrintf": false + }, + { + "expr": "mp_semaphore.block_base", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_memory_pool", + "isPrintf": false + }, + { + "expr": "mp_mpool.block_base", + "isPrintf": false + }, + { + "expr": "os_Info.mpi_message_queue", + "isPrintf": false + }, + { + "expr": "mp_mqueue.block_base", + "isPrintf": false + }, + { + "expr": "os_Config.mem_stack_addr", + "isPrintf": false + }, + { + "expr": "os_Config.mem_stack_addr + 8", + "isPrintf": false + }, + { + "expr": "os_Config.mem_mp_data_addr", + "isPrintf": false + }, + { + "expr": "os_Config.mem_mp_data_addr + 8", + "isPrintf": false + }, + { + "expr": "os_Config.mem_mq_data_addr", + "isPrintf": false + }, + { + "expr": "os_Config.mem_mq_data_addr + 8", + "isPrintf": false + }, + { + "expr": "os_Config.mem_common_addr", + "isPrintf": false + }, + { + "expr": "os_Config.mem_common_addr + 8", + "isPrintf": false + }, + { + "expr": "addr", + "isPrintf": false + }, + { + "expr": "os_Info.thread_wdog_list", + "isPrintf": false + }, + { + "expr": "TCB[i].delay_prev", + "isPrintf": false + }, + { + "expr": "CCB[i].prev", + "isPrintf": false + }, + { + "expr": "ECB[i].thread_list", + "isPrintf": false + }, + { + "expr": "MCB[i].thread_list", + "isPrintf": false + }, + { + "expr": "SCB[i].thread_list", + "isPrintf": false + }, + { + "expr": "PCB[i].thread_list", + "isPrintf": false + }, + { + "expr": "QCB[i].thread_list", + "isPrintf": false + }, + { + "expr": "QCB[i].msg_first", + "isPrintf": false + }, + { + "expr": "-1", + "isPrintf": false + }, + { + "expr": "RTX V%d[V_Major].%d[V_Minor].%d[V_Patch]", + "isPrintf": true + }, + { + "expr": "osKernelInactive", + "isPrintf": false + }, + { + "expr": "%E[os_Info.kernel_state]", + "isPrintf": true + }, + { + "expr": "osThreadPrivileged: %t[(os_Info.kernel_protect & 1) ? \"Disabled\" : \"Enabled\"]", + "isPrintf": true + }, + { + "expr": "osThreadPrivileged: %t[(os_Info.kernel_protect & 1) ? \"Disabled\" : \"Enabled\"], osSafetyClass(%d[os_Info.kernel_protect/16])", + "isPrintf": true + }, + { + "expr": "%d[os_Info.kernel_tick]", + "isPrintf": true + }, + { + "expr": "%d[os_Config.tick_freq]", + "isPrintf": true + }, + { + "expr": "Disabled", + "isPrintf": false + }, + { + "expr": "%d[os_Info.robin_tick]", + "isPrintf": true + }, + { + "expr": "%d[os_Config.robin_timeout]", + "isPrintf": true + }, + { + "expr": "Not used", + "isPrintf": false + }, + { + "expr": "Base: %x[mem_head_com._addr], Size: %d[mem_head_com.size], Used: %d[mem_head_com.used], Max used: %d[mem_head_com.max_used]", + "isPrintf": true + }, + { + "expr": "%t[os_Config.stack_check ? \"Enabled\" : \"Disabled\"]", + "isPrintf": true + }, + { + "expr": "%t[os_Config.stack_wmark ? \"Enabled\" : \"Disabled\"]", + "isPrintf": true + }, + { + "expr": "%d[os_Config.thread_stack_size]", + "isPrintf": true + }, + { + "expr": "Size: %d[os_Info.isr_queue_max], Used: %d[os_Info.isr_queue_cnt]", + "isPrintf": true + }, + { + "expr": "%x[ISR_FIFO[i]]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_thread.used_blocks], Max: %d[cfg_mp_thread.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_thread.block_base], Size: %d[cfg_mp_thread.block_lim - cfg_mp_thread.block_base], Used: %d[cfg_mp_thread.used_blocks * cfg_mp_thread.block_size]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_stack.block_base], Size: %d[cfg_mp_stack.block_lim - cfg_mp_stack.block_base], Used: %d[cfg_mp_stack.used_blocks * cfg_mp_stack.block_size]", + "isPrintf": true + }, + { + "expr": "Base: %x[mem_head_stack._addr], Size: %d[mem_head_stack.size], Used: %d[mem_head_stack.used], Max used: %d[mem_head_stack.max_used]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_timer.used_blocks], Max: %d[cfg_mp_timer.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_timer.block_base], Size: %d[cfg_mp_timer.block_lim - cfg_mp_timer.block_base], Used: %d[cfg_mp_timer.used_blocks * cfg_mp_timer.block_size]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_events.used_blocks], Max: %d[cfg_mp_events.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_events.block_base], Size: %d[cfg_mp_events.block_lim - cfg_mp_events.block_base], Used: %d[cfg_mp_events.used_blocks * cfg_mp_events.block_size]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_mutex.used_blocks], Max: %d[cfg_mp_mutex.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_mutex.block_base], Size: %d[cfg_mp_mutex.block_lim - cfg_mp_mutex.block_base], Used: %d[cfg_mp_mutex.used_blocks * cfg_mp_mutex.block_size]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_semaphore.used_blocks], Max: %d[cfg_mp_semaphore.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_semaphore.block_base], Size: %d[cfg_mp_semaphore.block_lim - cfg_mp_semaphore.block_base], Used: %d[cfg_mp_semaphore.used_blocks * cfg_mp_semaphore.block_size]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_mpool.used_blocks], Max: %d[cfg_mp_mpool.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_mpool.block_base], Size: %d[cfg_mp_mpool.block_lim - cfg_mp_mpool.block_base], Used: %d[cfg_mp_mpool.used_blocks * cfg_mp_mpool.block_size]", + "isPrintf": true + }, + { + "expr": "Base: %x[mem_head_mp_data._addr], Size: %d[mem_head_mp_data.size], Used: %d[mem_head_mp_data.used], Max used: %d[mem_head_mp_data.max_used]", + "isPrintf": true + }, + { + "expr": "Used: %d[cfg_mp_mqueue.used_blocks], Max: %d[cfg_mp_mqueue.max_blocks]", + "isPrintf": true + }, + { + "expr": "Base: %x[cfg_mp_mqueue.block_base], Size: %d[cfg_mp_mqueue.block_lim - cfg_mp_mqueue.block_base], Used: %d[cfg_mp_mqueue.used_blocks * cfg_mp_mqueue.block_size]", + "isPrintf": true + }, + { + "expr": "Base: %x[mem_head_mq_data._addr], Size: %d[mem_head_mq_data.size], Used: %d[mem_head_mq_data.used], Max used: %d[mem_head_mq_data.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_Thread.cnt_alloc], Free: %d[MUC_Thread.cnt_free], Max used: %d[MUC_Thread.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_Timer.cnt_alloc], Free: %d[MUC_Timer.cnt_free], Max used: %d[MUC_Timer.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_EventFlags.cnt_alloc], Free: %d[MUC_EventFlags.cnt_free], Max used: %d[MUC_EventFlags.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_Mutex.cnt_alloc], Free: %d[MUC_Mutex.cnt_free], Max used: %d[MUC_Mutex.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_Semaphore.cnt_alloc], Free: %d[MUC_Semaphore.cnt_free], Max used: %d[MUC_Semaphore.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_MemPool.cnt_alloc], Free: %d[MUC_MemPool.cnt_free], Max used: %d[MUC_MemPool.max_used]", + "isPrintf": true + }, + { + "expr": "Alloc: %d[MUC_MsgQueue.cnt_alloc], Free: %d[MUC_MsgQueue.cnt_free], Max used: %d[MUC_MsgQueue.max_used]", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state & 0x07], %E[TCB[i].priority], Stack Used: %d[TCB[i].stack_curp]%%", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state & 0x07], %E[TCB[i].priority], Stack Used: %d[TCB[i].stack_curp]%%, Max: %d[TCB[i].stack_maxp]%%", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state & 0x07], %E[TCB[i].priority], Stack Used: unknown", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state & 0x07], %E[TCB[i].priority], Stack Used: unknown, Max: %d[TCB[i].stack_maxp]%%", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state & 0x07]", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].priority]", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].attr & 0x01], %E[TCB[i].attr & 0x06]", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].attr & 0x01], %E[TCB[i].attr & 0x06], osSafetyClass(%d[TCB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].attr & 0x01], %E[TCB[i].attr & 0x06], osThreadZone(%d[TCB[i].zone])", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].attr & 0x01], %E[TCB[i].attr & 0x06], osSafetyClass(%d[TCB[i].attr/16]), osThreadZone(%d[TCB[i].zone])", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state], Timeout: %d[TCB[i].ex_delay]", + "isPrintf": true + }, + { + "expr": "%E[TCB[i].state], Timeout: osWaitForever", + "isPrintf": true + }, + { + "expr": "Used: unknown", + "isPrintf": false + }, + { + "expr": "Used: unknown, Max: %d[TCB[i].stack_maxp]%% [%d[TCB[i].stack_maxb]]", + "isPrintf": true + }, + { + "expr": "Used: %d[TCB[i].stack_curp]%% [%d[TCB[i].stack_curb]]", + "isPrintf": true + }, + { + "expr": "Used: %d[TCB[i].stack_curp]%% [%d[TCB[i].stack_curb]], Max: %d[TCB[i].stack_maxp]%% [%d[TCB[i].stack_maxb]]", + "isPrintf": true + }, + { + "expr": "unknown", + "isPrintf": false + }, + { + "expr": "%d[TCB[i].stack_curb]", + "isPrintf": true + }, + { + "expr": "%d[TCB[i].stack_maxb]", + "isPrintf": true + }, + { + "expr": "%x[TCB[i].stack_mem + TCB[i].stack_size]", + "isPrintf": true + }, + { + "expr": "%x[TCB[i].stack_cur]", + "isPrintf": true + }, + { + "expr": "%x[TCB[i].stack_mem]", + "isPrintf": true + }, + { + "expr": "%d[TCB[i].stack_size]", + "isPrintf": true + }, + { + "expr": "Overrun detected", + "isPrintf": false + }, + { + "expr": "%x[TCB[i].thread_flags]", + "isPrintf": true + }, + { + "expr": "Inactive", + "isPrintf": false + }, + { + "expr": "Running, Timeout: %d[TCB[i].wd_tick]", + "isPrintf": true + }, + { + "expr": "%x[TCB[i].wait_flags], %E[TCB[i].flags_options & 1]", + "isPrintf": true + }, + { + "expr": "%x[TCB[i].wait_flags], %E[TCB[i].flags_options & 1], osFlagsNoClear", + "isPrintf": true + }, + { + "expr": "%d[TCB[i].tz_memory]", + "isPrintf": true + }, + { + "expr": "%E[CCB[i].state], Tick: %d[CCB[i].ex_tick]", + "isPrintf": true + }, + { + "expr": "%E[CCB[i].state]", + "isPrintf": true + }, + { + "expr": "%E[CCB[i].attr & 0x01]", + "isPrintf": true + }, + { + "expr": "osSafetyClass(%d[CCB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "%d[CCB[i].ex_tick]", + "isPrintf": true + }, + { + "expr": "%d[CCB[i].load]", + "isPrintf": true + }, + { + "expr": "Func: %S[CCB[i].finfo_fp], Arg: %x[CCB[i].finfo_arg]", + "isPrintf": true + }, + { + "expr": "Tokens: %d[SCB[i].tokens], Max: %d[SCB[i].max_tokens]", + "isPrintf": true + }, + { + "expr": "osSafetyClass(%d[SCB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "%d[SCB[i].tokens]", + "isPrintf": true + }, + { + "expr": "%d[SCB[i].max_tokens]", + "isPrintf": true + }, + { + "expr": "Timeout: %d[TCB[k].ex_delay]", + "isPrintf": true + }, + { + "expr": "Timeout: osWaitForever", + "isPrintf": false + }, + { + "expr": "Lock counter: %d[MCB[i].lock]", + "isPrintf": true + }, + { + "expr": "%x[MCB[i].lock]", + "isPrintf": true + }, + { + "expr": "osSafetyClass(%d[MCB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "%t[(MCB[i].attr & 0x01) ? \"True\" : \"False\"]", + "isPrintf": true + }, + { + "expr": "id: %x[TCB[n]._addr] %N[TCB[n].name]", + "isPrintf": true + }, + { + "expr": "Flags: %x[ECB[i].event_flags]", + "isPrintf": true + }, + { + "expr": "osSafetyClass(%d[ECB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "Used: %d[PCB[i].used_blocks], Max: %d[PCB[i].max_blocks]", + "isPrintf": true + }, + { + "expr": "osSafetyClass(%d[PCB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "%d[PCB[i].used_blocks]", + "isPrintf": true + }, + { + "expr": "%d[PCB[i].max_blocks]", + "isPrintf": true + }, + { + "expr": "%d[PCB[i].block_size]", + "isPrintf": true + }, + { + "expr": "%x[PCB[i].block_base]", + "isPrintf": true + }, + { + "expr": "%d[PCB[i].block_lim - PCB[i].block_base]", + "isPrintf": true + }, + { + "expr": "Messages: %d[QCB[i].msg_count], Max: %d[QCB[i].max_blocks]", + "isPrintf": true + }, + { + "expr": "osSafetyClass(%d[QCB[i].attr/16])", + "isPrintf": true + }, + { + "expr": "%d[QCB[i].ml_cnt]", + "isPrintf": true + }, + { + "expr": "%d[QCB[i].max_blocks]", + "isPrintf": true + }, + { + "expr": "%d[QCB[i].msg_size]", + "isPrintf": true + }, + { + "expr": "Address: %x[QML[j + QCB[i].ml_idx].addr], Priority: %d[QML[j + QCB[i].ml_idx].priority]", + "isPrintf": true + }, + { + "expr": "mem=%x[val1], size=%d[val2], result=%d[val3]", + "isPrintf": true + }, + { + "expr": "mem=%x[val1], size=%d[val2], type=%d[val3], block=%x[val4]", + "isPrintf": true + }, + { + "expr": "mem=%x[val1], block=%x[val2], result=%d[val3]", + "isPrintf": true + }, + { + "expr": "mp_info=%x[val1], block_count=%d[val2], block_size=%d[val3], block_mem=%x[val4]", + "isPrintf": true + }, + { + "expr": "mp_info=%x[val1], block=%x[val2]", + "isPrintf": true + }, + { + "expr": "mp_info=%x[val1], block=%x[val2], status=%E[val3, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "status=%E[val1, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "version=%x[val1], id_buf=%x[val2], id_size=%d[val3]", + "isPrintf": true + }, + { + "expr": "version_api=%d[val1/10000000].%d[(val1/10000)%1000].%d[val1%10000], version_kernel=%d[val2/10000000].%d[(val2/10000)%1000].%d[val2%10000]", + "isPrintf": true + }, + { + "expr": "id=%t[val1]", + "isPrintf": true + }, + { + "expr": "state=%E[val1, rtx_kernel_state:id]", + "isPrintf": true + }, + { + "expr": "lock=%d[val1]", + "isPrintf": true + }, + { + "expr": "sleep_ticks=%d[val1]", + "isPrintf": true + }, + { + "expr": "safety_class=%d[val1]", + "isPrintf": true + }, + { + "expr": "count=%d[val1]", + "isPrintf": true + }, + { + "expr": "freq=%d[val1]", + "isPrintf": true + }, + { + "expr": "code=%E[val1, rtx_error:id], object_id=%x[val2]", + "isPrintf": true + }, + { + "expr": "safety_class=%d[val1], mode=%x[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "func=%S[val1], argument=%x[val2], attr=%x[val3]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], safety_class=%d[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], zone=%d[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], state=%E[val2, rtx_th_state:id]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], stack_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], stack_space=%d[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], priority=%E[val2, rtx_th_priority:id]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], timeout=%d[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], ret_val=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "ticks=%d[val1]", + "isPrintf": true + }, + { + "expr": "thread_array=%x[val1], array_items=%d[val2], count=%d[val3]", + "isPrintf": true + }, + { + "expr": "zone=%d[val1]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], flags=%x[val2]", + "isPrintf": true + }, + { + "expr": "thread_id=%x[val1], thread_flags=%x[val2]", + "isPrintf": true + }, + { + "expr": "flags=%x[val1]", + "isPrintf": true + }, + { + "expr": "thread_flags=%x[val1]", + "isPrintf": true + }, + { + "expr": "flags=%x[val1], options=%x[val2], timeout=%d[val3]", + "isPrintf": true + }, + { + "expr": "flags=%x[val1], options=%x[val2], thread_flags=%x[val3], thread_id=%x[val4]", + "isPrintf": true + }, + { + "expr": "flags=%x[val1], options=%x[val2]", + "isPrintf": true + }, + { + "expr": "timer_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "func=%S[val1], argument=%x[val2]", + "isPrintf": true + }, + { + "expr": "func=%S[val1], type=%E[val2, rtx_timer_type:id], argument=%x[val3], attr=%x[val4]", + "isPrintf": true + }, + { + "expr": "timer_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "timer_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "timer_id=%x[val1], ticks=%d[val2]", + "isPrintf": true + }, + { + "expr": "timer_id=%x[val1], running=%d[val2]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "attr=%x[val1]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], flags=%x[val2]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], event_flags=%x[val2]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], flags=%x[val2], options=%x[val3], timeout=%d[val4]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], flags=%x[val2], options=%x[val3], event_flags=%x[val4]", + "isPrintf": true + }, + { + "expr": "ef_id=%x[val1], flags=%x[val2], options=%x[val3]", + "isPrintf": true + }, + { + "expr": "mutex_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "mutex_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "mutex_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "mutex_id=%x[val1], timeout=%d[val2]", + "isPrintf": true + }, + { + "expr": "mutex_id=%x[val1], lock=%d[val2]", + "isPrintf": true + }, + { + "expr": "mutex_id=%x[val1], thread_id=%x[val2]", + "isPrintf": true + }, + { + "expr": "semaphore_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "max_count=%d[val1], initial_count=%d[val2], attr=%x[val3]", + "isPrintf": true + }, + { + "expr": "semaphore_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "semaphore_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "semaphore_id=%x[val1], timeout=%d[val2]", + "isPrintf": true + }, + { + "expr": "semaphore_id=%x[val1], tokens=%d[val2]", + "isPrintf": true + }, + { + "expr": "semaphore_id=%x[val1], count=%d[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "block_count=%d[val1], block_size=%d[val2], attr=%x[val3]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], timeout=%d[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], block=%x[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], capacity=%d[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], block_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], count=%d[val2]", + "isPrintf": true + }, + { + "expr": "mp_id=%x[val1], space=%d[val2]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], status=%E[val2, rtx_t:status]", + "isPrintf": true + }, + { + "expr": "msg_count=%d[val1], msg_size=%d[val2], attr=%x[val3]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], name=%N[val2]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], msg_ptr=%x[val2], msg_prio=%d[val3], timeout=%d[val4]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], msg_ptr=%x[val2], timeout=%d[val3]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], msg_ptr=%x[val2]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], msg_ptr=%x[val2], msg_prio=%x[val3], timeout=%d[val4]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], capacity=%d[val2]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], msg_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], count=%d[val2]", + "isPrintf": true + }, + { + "expr": "mq_id=%x[val1], space=%d[val2]", + "isPrintf": true + }, + { + "expr": "os_Config.isr_queue_max", + "isPrintf": false + }, + { + "expr": "((os_Info.version / 10000000) == 5) && (os_Info.kernel_state > 0) && (os_Info.kernel_state < 5)", + "isPrintf": false + }, + { + "expr": "RTX_En", + "isPrintf": false + }, + { + "expr": "RTX_En && (os_Config.mpi_thread || os_Config.mpi_timer || os_Config.mpi_event_flags || os_Config.mpi_mutex || os_Config.mpi_semaphore || os_Config.mpi_memory_pool || os_Config.mpi_message_queue)", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"os_cb_sections\")", + "isPrintf": false + }, + { + "expr": "RTX_En && __Symbol_exists (\"os_cb_sections\")", + "isPrintf": false + }, + { + "expr": "TCB_Rd", + "isPrintf": false + }, + { + "expr": "CCB_Rd", + "isPrintf": false + }, + { + "expr": "ECB_Rd", + "isPrintf": false + }, + { + "expr": "MCB_Rd", + "isPrintf": false + }, + { + "expr": "SCB_Rd", + "isPrintf": false + }, + { + "expr": "PCB_Rd", + "isPrintf": false + }, + { + "expr": "QCB_Rd", + "isPrintf": false + }, + { + "expr": "RTX_En && (TCB_Rd == 0) && os_Info.thread_idle", + "isPrintf": false + }, + { + "expr": "RTX_En && (TCB_Rd == 0) && os_Info.timer_thread", + "isPrintf": false + }, + { + "expr": "RTX_En && (TCB_Rd == 0) && os_Info.mpi_thread", + "isPrintf": false + }, + { + "expr": "RTX_En && (CCB_Rd == 0) && os_Info.mpi_timer", + "isPrintf": false + }, + { + "expr": "RTX_En && (ECB_Rd == 0) && os_Info.mpi_event_flags", + "isPrintf": false + }, + { + "expr": "RTX_En && (MCB_Rd == 0) && os_Info.mpi_mutex", + "isPrintf": false + }, + { + "expr": "RTX_En && (SCB_Rd == 0) && os_Info.mpi_semaphore", + "isPrintf": false + }, + { + "expr": "RTX_En && (PCB_Rd == 0) && os_Info.mpi_memory_pool", + "isPrintf": false + }, + { + "expr": "RTX_En && (QCB_Rd == 0) && os_Info.mpi_message_queue", + "isPrintf": false + }, + { + "expr": "RTX_En && os_Config.mem_stack_addr", + "isPrintf": false + }, + { + "expr": "RTX_En && os_Config.mem_mp_data_addr", + "isPrintf": false + }, + { + "expr": "RTX_En && os_Config.mem_mq_data_addr", + "isPrintf": false + }, + { + "expr": "RTX_En && os_Config.mem_common_addr", + "isPrintf": false + }, + { + "expr": "mem_list_com._count > 1", + "isPrintf": false + }, + { + "expr": "(mem_list_com[i].len & 1) && (mem_list_com[i].id == 0xF1)", + "isPrintf": false + }, + { + "expr": "RTX_En && os_Config.watchdog && os_Info.thread_wdog_list", + "isPrintf": false + }, + { + "expr": "(TCB[i].state == 2) && os_Config.robin_timeout", + "isPrintf": false + }, + { + "expr": "(TCB[i].state == 2) && (__Running == 0)", + "isPrintf": false + }, + { + "expr": "TCB[i].state != 2", + "isPrintf": false + }, + { + "expr": "TCB[i].sp != 0", + "isPrintf": false + }, + { + "expr": "(os_Config.stack_wmark != 0) && (TCB[i].sp != 0) && (TCB[i].stack_over == 0) && (TCB[i].stack_size < 65536)", + "isPrintf": false + }, + { + "expr": "(TCB[i].sp != 0) && (TCB[i].stack_over != 0)", + "isPrintf": false + }, + { + "expr": "(TCB[i].sp != 0) && (TCB[i].stack_curb >= TCB[i].stack_maxb)", + "isPrintf": false + }, + { + "expr": "TCB[i].delay != -1", + "isPrintf": false + }, + { + "expr": "os_Config.watchdog", + "isPrintf": false + }, + { + "expr": "k == 0", + "isPrintf": false + }, + { + "expr": "TCB[i]._addr == WDL[j]._addr", + "isPrintf": false + }, + { + "expr": "os_Config.safety_feat", + "isPrintf": false + }, + { + "expr": "MCB._count", + "isPrintf": false + }, + { + "expr": "SCB._count", + "isPrintf": false + }, + { + "expr": "PCB._count", + "isPrintf": false + }, + { + "expr": "QCB._count", + "isPrintf": false + }, + { + "expr": "QML._count", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxThreadMemUsage\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxTimerMemUsage\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxEventFlagsMemUsage\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxMutexMemUsage\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxSemaphoreMemUsage\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxMemoryPoolMemUsage\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists (\"osRtxMessageQueueMemUsage\")", + "isPrintf": false + }, + { + "expr": "MUC_Thread_En", + "isPrintf": false + }, + { + "expr": "MUC_Timer_En", + "isPrintf": false + }, + { + "expr": "MUC_EventFlags_En", + "isPrintf": false + }, + { + "expr": "MUC_Mutex_En", + "isPrintf": false + }, + { + "expr": "MUC_Semaphore_En", + "isPrintf": false + }, + { + "expr": "MUC_MemPool_En", + "isPrintf": false + }, + { + "expr": "MUC_MsgQueue_En", + "isPrintf": false + }, + { + "expr": "TCB._count", + "isPrintf": false + }, + { + "expr": "TCB[i].cb_valid == 0", + "isPrintf": false + }, + { + "expr": "TCB[i].cb_valid && (TCB[i].name == 0) && (os_Config.stack_wmark == 0)", + "isPrintf": false + }, + { + "expr": "TCB[i].cb_valid && (TCB[i].name == 0) && (os_Config.stack_wmark != 0)", + "isPrintf": false + }, + { + "expr": "TCB[i].cb_valid && (TCB[i].name != 0) && (os_Config.stack_wmark == 0)", + "isPrintf": false + }, + { + "expr": "TCB[i].cb_valid && (TCB[i].name != 0) && (os_Config.stack_wmark != 0)", + "isPrintf": false + }, + { + "expr": "TCB[i].sp_valid == 0", + "isPrintf": false + }, + { + "expr": "CCB._count", + "isPrintf": false + }, + { + "expr": "ECB._count", + "isPrintf": false + }, + { + "expr": "RTX_En != 0", + "isPrintf": false + }, + { + "expr": "RTX_En == 0", + "isPrintf": false + }, + { + "expr": "(os_Config.safety_feat == 1) && (os_Config.safety_class == 0) && (RTX_En != 0)", + "isPrintf": false + }, + { + "expr": "(os_Config.robin_timeout == 0) && (RTX_En != 0)", + "isPrintf": false + }, + { + "expr": "(os_Config.robin_timeout > 0) && (RTX_En != 0)", + "isPrintf": false + }, + { + "expr": "(os_Config.mem_common_size == 0) && (RTX_En != 0)", + "isPrintf": false + }, + { + "expr": "(os_Config.mem_common_size != 0) && (RTX_En != 0)", + "isPrintf": false + }, + { + "expr": "StaticMp_En", + "isPrintf": false + }, + { + "expr": "os_Config.mem_stack_size", + "isPrintf": false + }, + { + "expr": "os_Config.mem_mp_data_size", + "isPrintf": false + }, + { + "expr": "os_Config.mem_mq_data_size", + "isPrintf": false + }, + { + "expr": "(MUC_En != 0) && (RTX_En != 0)", + "isPrintf": false + }, + { + "expr": "TCB_En", + "isPrintf": false + }, + { + "expr": "TCB[i].out_type == 1", + "isPrintf": false + }, + { + "expr": "(os_Config.exec_zone == 0) && (os_Config.safety_class == 0)", + "isPrintf": false + }, + { + "expr": "((TCB[i].state & 0x07) == 3) && (TCB[i].ex_delay != -1)", + "isPrintf": false + }, + { + "expr": "((TCB[i].state & 0x07) == 3) && (TCB[i].ex_delay == -1)", + "isPrintf": false + }, + { + "expr": "TCB[i].state == 0x23", + "isPrintf": false + }, + { + "expr": "TCB[i].thread_prev == ECB[n]._addr", + "isPrintf": false + }, + { + "expr": "TCB[i].thread_prev == MCB[n]._addr", + "isPrintf": false + }, + { + "expr": "TCB[i].thread_prev == SCB[n]._addr", + "isPrintf": false + }, + { + "expr": "TCB[i].thread_prev == PCB[n]._addr", + "isPrintf": false + }, + { + "expr": "(TCB[i].state == 0x83) || (TCB[i].state == 0x84)", + "isPrintf": false + }, + { + "expr": "TCB[i].thread_prev == QCB[n]._addr", + "isPrintf": false + }, + { + "expr": "(TCB[i].sp_valid == 0) && (os_Config.stack_wmark == 0)", + "isPrintf": false + }, + { + "expr": "(TCB[i].sp_valid == 0) && (os_Config.stack_wmark != 0)", + "isPrintf": false + }, + { + "expr": "os_Config.stack_wmark != 0", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_over != 0", + "isPrintf": false + }, + { + "expr": "(os_Config.watchdog != 0) && (TCB[i].wd_state == 0)", + "isPrintf": false + }, + { + "expr": "(TCB[i].wait_flags != 0) && ((TCB[i].flags_options & 2) == 0)", + "isPrintf": false + }, + { + "expr": "(TCB[i].wait_flags != 0) && ((TCB[i].flags_options & 2) != 0)", + "isPrintf": false + }, + { + "expr": "TCB[i].tz_memory", + "isPrintf": false + }, + { + "expr": "CCB_En", + "isPrintf": false + }, + { + "expr": "CCB[i].cb_valid", + "isPrintf": false + }, + { + "expr": "os_Config.safety_class == 1", + "isPrintf": false + }, + { + "expr": "SCB_En", + "isPrintf": false + }, + { + "expr": "SCB[i].cb_valid", + "isPrintf": false + }, + { + "expr": "SCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "(SWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay != -1)", + "isPrintf": false + }, + { + "expr": "(SWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay == -1)", + "isPrintf": false + }, + { + "expr": "MCB_En", + "isPrintf": false + }, + { + "expr": "MCB[i].cb_valid", + "isPrintf": false + }, + { + "expr": "MCB[i].lock", + "isPrintf": false + }, + { + "expr": "MCB[i].owner_thread == TCB[n]._addr", + "isPrintf": false + }, + { + "expr": "MCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "(MWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay != -1)", + "isPrintf": false + }, + { + "expr": "(MWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay == -1)", + "isPrintf": false + }, + { + "expr": "ECB_En", + "isPrintf": false + }, + { + "expr": "ECB[i].cb_valid", + "isPrintf": false + }, + { + "expr": "ECB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "(EWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay != -1)", + "isPrintf": false + }, + { + "expr": "(EWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay == -1)", + "isPrintf": false + }, + { + "expr": "PCB_En", + "isPrintf": false + }, + { + "expr": "PCB[i].cb_valid", + "isPrintf": false + }, + { + "expr": "PCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "(PWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay != -1)", + "isPrintf": false + }, + { + "expr": "(PWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay == -1)", + "isPrintf": false + }, + { + "expr": "QCB_En", + "isPrintf": false + }, + { + "expr": "QCB[i].cb_valid", + "isPrintf": false + }, + { + "expr": "QCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "(QWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay != -1)", + "isPrintf": false + }, + { + "expr": "(QWL[j].stack_mem == TCB[k].stack_mem) && (TCB[k].ex_delay == -1)", + "isPrintf": false + }, + { + "expr": "QCB[i].ml_cnt", + "isPrintf": false + }, + { + "expr": "osRtxInfo", + "isPrintf": false + }, + { + "expr": "osRtxConfig", + "isPrintf": false + }, + { + "expr": "os_cb_sections", + "isPrintf": false + }, + { + "expr": "osRtxThreadMemUsage", + "isPrintf": false + }, + { + "expr": "osRtxTimerMemUsage", + "isPrintf": false + }, + { + "expr": "osRtxEventFlagsMemUsage", + "isPrintf": false + }, + { + "expr": "osRtxMutexMemUsage", + "isPrintf": false + }, + { + "expr": "osRtxSemaphoreMemUsage", + "isPrintf": false + }, + { + "expr": "osRtxMemoryPoolMemUsage", + "isPrintf": false + }, + { + "expr": "osRtxMessageQueueMemUsage", + "isPrintf": false + }, + { + "expr": "mp_thread.max_blocks", + "isPrintf": false + }, + { + "expr": "mp_timer.max_blocks", + "isPrintf": false + }, + { + "expr": "mp_events.max_blocks", + "isPrintf": false + }, + { + "expr": "mp_mutex.max_blocks", + "isPrintf": false + }, + { + "expr": "mp_semaphore.max_blocks", + "isPrintf": false + }, + { + "expr": "mp_mpool.max_blocks", + "isPrintf": false + }, + { + "expr": "mp_mqueue.max_blocks", + "isPrintf": false + }, + { + "expr": "SCB[i].wl_idx", + "isPrintf": false + }, + { + "expr": "MCB[i].wl_idx", + "isPrintf": false + }, + { + "expr": "ECB[i].wl_idx", + "isPrintf": false + }, + { + "expr": "PCB[i].wl_idx", + "isPrintf": false + }, + { + "expr": "QCB[i].wl_idx", + "isPrintf": false + }, + { + "expr": "mem_list_com._count-1", + "isPrintf": false + }, + { + "expr": "TDL._count", + "isPrintf": false + }, + { + "expr": "WDL._count", + "isPrintf": false + }, + { + "expr": "TEL._count", + "isPrintf": false + }, + { + "expr": "os_Info.isr_queue_cnt", + "isPrintf": false + }, + { + "expr": "SCB[i].wl_idx + SCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "MCB[i].wl_idx + MCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "ECB[i].wl_idx + ECB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "PCB[i].wl_idx + PCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "QCB[i].wl_idx + QCB[i].wl_cnt", + "isPrintf": false + }, + { + "expr": "next", + "isPrintf": false + }, + { + "expr": "wdog_next", + "isPrintf": false + }, + { + "expr": "delay_prev", + "isPrintf": false + }, + { + "expr": "prev", + "isPrintf": false + }, + { + "expr": "thread_next", + "isPrintf": false + }, + { + "expr": "System", + "isPrintf": true + }, + { + "expr": "Kernel ID", + "isPrintf": true + }, + { + "expr": "Kernel State", + "isPrintf": true + }, + { + "expr": "Kernel Protect", + "isPrintf": true + }, + { + "expr": "Kernel Tick Count", + "isPrintf": true + }, + { + "expr": "Kernel Tick Frequency", + "isPrintf": true + }, + { + "expr": "Round Robin", + "isPrintf": true + }, + { + "expr": "Round Robin Tick Count", + "isPrintf": true + }, + { + "expr": "Round Robin Timeout", + "isPrintf": true + }, + { + "expr": "Global Dynamic Memory", + "isPrintf": true + }, + { + "expr": "Stack Overrun Check", + "isPrintf": true + }, + { + "expr": "Stack Usage Watermark", + "isPrintf": true + }, + { + "expr": "Default Thread Stack Size", + "isPrintf": true + }, + { + "expr": "ISR FIFO Queue", + "isPrintf": true + }, + { + "expr": "data[%d[i]]", + "isPrintf": true + }, + { + "expr": "Object specific Memory allocation", + "isPrintf": true + }, + { + "expr": "Thread objects", + "isPrintf": true + }, + { + "expr": "Control blocks", + "isPrintf": true + }, + { + "expr": "Default stack", + "isPrintf": true + }, + { + "expr": "User stack", + "isPrintf": true + }, + { + "expr": "Timer objects", + "isPrintf": true + }, + { + "expr": "Event Flags objects", + "isPrintf": true + }, + { + "expr": "Mutex objects", + "isPrintf": true + }, + { + "expr": "Semaphore objects", + "isPrintf": true + }, + { + "expr": "Memory Pool objects", + "isPrintf": true + }, + { + "expr": "Data storage", + "isPrintf": true + }, + { + "expr": "Message Queue objects", + "isPrintf": true + }, + { + "expr": "Object Memory usage counters", + "isPrintf": true + }, + { + "expr": "Threads", + "isPrintf": true + }, + { + "expr": "id: %x[TCB[i]._addr] \"%S[TCB[i].thread_addr]\"", + "isPrintf": true + }, + { + "expr": "id: %x[TCB[i]._addr] %N[TCB[i].name]", + "isPrintf": true + }, + { + "expr": "State", + "isPrintf": true + }, + { + "expr": "Priority", + "isPrintf": true + }, + { + "expr": "Attributes", + "isPrintf": true + }, + { + "expr": "Waiting", + "isPrintf": true + }, + { + "expr": "id: %x[TCB[i].thread_join]", + "isPrintf": true + }, + { + "expr": "id: %x[TCB[i].thread_prev]", + "isPrintf": true + }, + { + "expr": "id: %x[ECB[n]._addr] %N[ECB[n].name]", + "isPrintf": true + }, + { + "expr": "id: %x[MCB[n]._addr] %N[MCB[n].name]", + "isPrintf": true + }, + { + "expr": "id: %x[SCB[n]._addr] %N[SCB[n].name]", + "isPrintf": true + }, + { + "expr": "id: %x[PCB[n]._addr] %N[PCB[n].name]", + "isPrintf": true + }, + { + "expr": "id: %x[QCB[n]._addr] %N[QCB[n].name]", + "isPrintf": true + }, + { + "expr": "Stack", + "isPrintf": true + }, + { + "expr": "Used", + "isPrintf": true + }, + { + "expr": "Max", + "isPrintf": true + }, + { + "expr": "Top", + "isPrintf": true + }, + { + "expr": "Current", + "isPrintf": true + }, + { + "expr": "Limit", + "isPrintf": true + }, + { + "expr": "Size", + "isPrintf": true + }, + { + "expr": "Stack Overrun", + "isPrintf": true + }, + { + "expr": "Flags", + "isPrintf": true + }, + { + "expr": "Watchdog", + "isPrintf": true + }, + { + "expr": "Wait Flags", + "isPrintf": true + }, + { + "expr": "TrustZone ID", + "isPrintf": true + }, + { + "expr": "Timers", + "isPrintf": true + }, + { + "expr": "id: %x[CCB[i]._addr] %N[CCB[i].name]", + "isPrintf": true + }, + { + "expr": "Type", + "isPrintf": true + }, + { + "expr": "Tick", + "isPrintf": true + }, + { + "expr": "Load", + "isPrintf": true + }, + { + "expr": "Callback", + "isPrintf": true + }, + { + "expr": "Semaphores", + "isPrintf": true + }, + { + "expr": "id: %x[SCB[i]._addr] %N[SCB[i].name]", + "isPrintf": true + }, + { + "expr": "Tokens", + "isPrintf": true + }, + { + "expr": "Max Tokens", + "isPrintf": true + }, + { + "expr": "Threads waiting (%d[SCB[i].wl_cnt])", + "isPrintf": true + }, + { + "expr": "id: %x[TCB[k]._addr] %N[TCB[k].name]", + "isPrintf": true + }, + { + "expr": "Mutexes", + "isPrintf": true + }, + { + "expr": "id: %x[MCB[i]._addr] %N[MCB[i].name]", + "isPrintf": true + }, + { + "expr": "Lock counter", + "isPrintf": true + }, + { + "expr": "osMutexRecursive", + "isPrintf": true + }, + { + "expr": "osMutexPrioInherit", + "isPrintf": true + }, + { + "expr": "osMutexRobust", + "isPrintf": true + }, + { + "expr": "Owner thread", + "isPrintf": true + }, + { + "expr": "Threads waiting (%d[MCB[i].wl_cnt])", + "isPrintf": true + }, + { + "expr": "Event Flags", + "isPrintf": true + }, + { + "expr": "id: %x[ECB[i]._addr] %N[ECB[i].name]", + "isPrintf": true + }, + { + "expr": "Threads waiting (%d[ECB[i].wl_cnt])", + "isPrintf": true + }, + { + "expr": "Memory Pools", + "isPrintf": true + }, + { + "expr": "id: %x[PCB[i]._addr] %N[PCB[i].name]", + "isPrintf": true + }, + { + "expr": "Used blocks", + "isPrintf": true + }, + { + "expr": "Max blocks", + "isPrintf": true + }, + { + "expr": "Block size", + "isPrintf": true + }, + { + "expr": "Memory base", + "isPrintf": true + }, + { + "expr": "Memory size", + "isPrintf": true + }, + { + "expr": "Threads waiting (%d[PCB[i].wl_cnt])", + "isPrintf": true + }, + { + "expr": "Message Queues", + "isPrintf": true + }, + { + "expr": "id: %x[QCB[i]._addr] %N[QCB[i].name]", + "isPrintf": true + }, + { + "expr": "Messages", + "isPrintf": true + }, + { + "expr": "Max Messages", + "isPrintf": true + }, + { + "expr": "Message size", + "isPrintf": true + }, + { + "expr": "Threads waiting (%d[QCB[i].wl_cnt])", + "isPrintf": true + }, + { + "expr": "Queue (%d[QCB[i].ml_cnt])", + "isPrintf": true + }, + { + "expr": "Queue[%d[j]]", + "isPrintf": true + }, + { + "expr": "MemoryInit", + "isPrintf": true + }, + { + "expr": "MemoryAlloc", + "isPrintf": true + }, + { + "expr": "MemoryFree", + "isPrintf": true + }, + { + "expr": "MemoryBlockInit", + "isPrintf": true + }, + { + "expr": "MemoryBlockAlloc", + "isPrintf": true + }, + { + "expr": "MemoryBlockFree", + "isPrintf": true + }, + { + "expr": "KernelError", + "isPrintf": true + }, + { + "expr": "KernelInitialize", + "isPrintf": true + }, + { + "expr": "KernelInitialized", + "isPrintf": true + }, + { + "expr": "KernelGetInfo", + "isPrintf": true + }, + { + "expr": "KernelInfoRetrieved", + "isPrintf": true + }, + { + "expr": "KernelGetState", + "isPrintf": true + }, + { + "expr": "KernelStart", + "isPrintf": true + }, + { + "expr": "KernelStarted", + "isPrintf": true + }, + { + "expr": "KernelLock", + "isPrintf": true + }, + { + "expr": "KernelLocked", + "isPrintf": true + }, + { + "expr": "KernelUnlock", + "isPrintf": true + }, + { + "expr": "KernelUnlocked", + "isPrintf": true + }, + { + "expr": "KernelRestoreLock", + "isPrintf": true + }, + { + "expr": "KernelLockRestored", + "isPrintf": true + }, + { + "expr": "KernelSuspend", + "isPrintf": true + }, + { + "expr": "KernelSuspended", + "isPrintf": true + }, + { + "expr": "KernelResume", + "isPrintf": true + }, + { + "expr": "KernelResumed", + "isPrintf": true + }, + { + "expr": "KernelProtect", + "isPrintf": true + }, + { + "expr": "KernelProtected", + "isPrintf": true + }, + { + "expr": "KernelGetTickCount", + "isPrintf": true + }, + { + "expr": "KernelGetTickFreq", + "isPrintf": true + }, + { + "expr": "KernelGetSysTimerCount", + "isPrintf": true + }, + { + "expr": "KernelGetSysTimerFreq", + "isPrintf": true + }, + { + "expr": "KernelErrorNotify", + "isPrintf": true + }, + { + "expr": "KernelDestroyClass", + "isPrintf": true + }, + { + "expr": "ThreadError", + "isPrintf": true + }, + { + "expr": "ThreadNew", + "isPrintf": true + }, + { + "expr": "ThreadCreated", + "isPrintf": true + }, + { + "expr": "ThreadGetName", + "isPrintf": true + }, + { + "expr": "ThreadGetClass", + "isPrintf": true + }, + { + "expr": "ThreadGetZone", + "isPrintf": true + }, + { + "expr": "ThreadGetId", + "isPrintf": true + }, + { + "expr": "ThreadGetState", + "isPrintf": true + }, + { + "expr": "ThreadGetStackSize", + "isPrintf": true + }, + { + "expr": "ThreadGetStackSpace", + "isPrintf": true + }, + { + "expr": "ThreadSetPriority", + "isPrintf": true + }, + { + "expr": "ThreadPriorityUpdated", + "isPrintf": true + }, + { + "expr": "ThreadGetPriority", + "isPrintf": true + }, + { + "expr": "ThreadYield", + "isPrintf": true + }, + { + "expr": "ThreadSuspend", + "isPrintf": true + }, + { + "expr": "ThreadSuspended", + "isPrintf": true + }, + { + "expr": "ThreadResume", + "isPrintf": true + }, + { + "expr": "ThreadResumed", + "isPrintf": true + }, + { + "expr": "ThreadDetach", + "isPrintf": true + }, + { + "expr": "ThreadDetached", + "isPrintf": true + }, + { + "expr": "ThreadJoin", + "isPrintf": true + }, + { + "expr": "ThreadJoinPending", + "isPrintf": true + }, + { + "expr": "ThreadJoined", + "isPrintf": true + }, + { + "expr": "ThreadBlocked", + "isPrintf": true + }, + { + "expr": "ThreadUnblocked", + "isPrintf": true + }, + { + "expr": "ThreadPreempted", + "isPrintf": true + }, + { + "expr": "ThreadSwitched", + "isPrintf": true + }, + { + "expr": "ThreadExit", + "isPrintf": true + }, + { + "expr": "ThreadTerminate", + "isPrintf": true + }, + { + "expr": "ThreadDestroyed", + "isPrintf": true + }, + { + "expr": "ThreadFeedWatchdog", + "isPrintf": true + }, + { + "expr": "ThreadFeedWatchdogDone", + "isPrintf": true + }, + { + "expr": "ThreadProtectPrivileged", + "isPrintf": true + }, + { + "expr": "ThreadPrivilegedProtected", + "isPrintf": true + }, + { + "expr": "ThreadGetCount", + "isPrintf": true + }, + { + "expr": "ThreadEnumerate", + "isPrintf": true + }, + { + "expr": "ThreadSuspendClass", + "isPrintf": true + }, + { + "expr": "ThreadResumeClass", + "isPrintf": true + }, + { + "expr": "ThreadTerminateZone", + "isPrintf": true + }, + { + "expr": "ThreadWatchdogExpired", + "isPrintf": true + }, + { + "expr": "ThreadFlagsError", + "isPrintf": true + }, + { + "expr": "ThreadFlagsSet", + "isPrintf": true + }, + { + "expr": "ThreadFlagsSetDone", + "isPrintf": true + }, + { + "expr": "ThreadFlagsClear", + "isPrintf": true + }, + { + "expr": "ThreadFlagsClearDone", + "isPrintf": true + }, + { + "expr": "ThreadFlagsGet", + "isPrintf": true + }, + { + "expr": "ThreadFlagsWait", + "isPrintf": true + }, + { + "expr": "ThreadFlagsWaitPending", + "isPrintf": true + }, + { + "expr": "ThreadFlagsWaitTimeout", + "isPrintf": true + }, + { + "expr": "ThreadFlagsWaitCompleted", + "isPrintf": true + }, + { + "expr": "ThreadFlagsWaitNotCompleted", + "isPrintf": true + }, + { + "expr": "DelayError", + "isPrintf": true + }, + { + "expr": "Delay", + "isPrintf": true + }, + { + "expr": "DelayUntil", + "isPrintf": true + }, + { + "expr": "DelayStarted", + "isPrintf": true + }, + { + "expr": "DelayUntilStarted", + "isPrintf": true + }, + { + "expr": "DelayCompleted", + "isPrintf": true + }, + { + "expr": "TimerError", + "isPrintf": true + }, + { + "expr": "TimerCallback", + "isPrintf": true + }, + { + "expr": "TimerNew", + "isPrintf": true + }, + { + "expr": "TimerCreated", + "isPrintf": true + }, + { + "expr": "TimerGetName", + "isPrintf": true + }, + { + "expr": "TimerStart", + "isPrintf": true + }, + { + "expr": "TimerStarted", + "isPrintf": true + }, + { + "expr": "TimerStop", + "isPrintf": true + }, + { + "expr": "TimerStopped", + "isPrintf": true + }, + { + "expr": "TimerIsRunning", + "isPrintf": true + }, + { + "expr": "TimerDelete", + "isPrintf": true + }, + { + "expr": "TimerDestroyed", + "isPrintf": true + }, + { + "expr": "EventFlagsError", + "isPrintf": true + }, + { + "expr": "EventFlagsNew", + "isPrintf": true + }, + { + "expr": "EventFlagsCreated", + "isPrintf": true + }, + { + "expr": "EventFlagsGetName", + "isPrintf": true + }, + { + "expr": "EventFlagsSet", + "isPrintf": true + }, + { + "expr": "EventFlagsSetDone", + "isPrintf": true + }, + { + "expr": "EventFlagsClear", + "isPrintf": true + }, + { + "expr": "EventFlagsClearDone", + "isPrintf": true + }, + { + "expr": "EventFlagsGet", + "isPrintf": true + }, + { + "expr": "EventFlagsWait", + "isPrintf": true + }, + { + "expr": "EventFlagsWaitPending", + "isPrintf": true + }, + { + "expr": "EventFlagsWaitTimeout", + "isPrintf": true + }, + { + "expr": "EventFlagsWaitCompleted", + "isPrintf": true + }, + { + "expr": "EventFlagsWaitNotCompleted", + "isPrintf": true + }, + { + "expr": "EventFlagsDelete", + "isPrintf": true + }, + { + "expr": "EventFlagsDestroyed", + "isPrintf": true + }, + { + "expr": "MutexError", + "isPrintf": true + }, + { + "expr": "MutexNew", + "isPrintf": true + }, + { + "expr": "MutexCreated", + "isPrintf": true + }, + { + "expr": "MutexGetName", + "isPrintf": true + }, + { + "expr": "MutexAcquire", + "isPrintf": true + }, + { + "expr": "MutexAcquirePending", + "isPrintf": true + }, + { + "expr": "MutexAcquireTimeout", + "isPrintf": true + }, + { + "expr": "MutexAcquired", + "isPrintf": true + }, + { + "expr": "MutexNotAcquired", + "isPrintf": true + }, + { + "expr": "MutexRelease", + "isPrintf": true + }, + { + "expr": "MutexReleased", + "isPrintf": true + }, + { + "expr": "MutexGetOwner", + "isPrintf": true + }, + { + "expr": "MutexDelete", + "isPrintf": true + }, + { + "expr": "MutexDestroyed", + "isPrintf": true + }, + { + "expr": "SemaphoreError", + "isPrintf": true + }, + { + "expr": "SemaphoreNew", + "isPrintf": true + }, + { + "expr": "SemaphoreCreated", + "isPrintf": true + }, + { + "expr": "SemaphoreGetName", + "isPrintf": true + }, + { + "expr": "SemaphoreAcquire", + "isPrintf": true + }, + { + "expr": "SemaphoreAcquirePending", + "isPrintf": true + }, + { + "expr": "SemaphoreAcquireTimeout", + "isPrintf": true + }, + { + "expr": "SemaphoreAcquired", + "isPrintf": true + }, + { + "expr": "SemaphoreNotAcquired", + "isPrintf": true + }, + { + "expr": "SemaphoreRelease", + "isPrintf": true + }, + { + "expr": "SemaphoreReleased", + "isPrintf": true + }, + { + "expr": "SemaphoreGetCount", + "isPrintf": true + }, + { + "expr": "SemaphoreDelete", + "isPrintf": true + }, + { + "expr": "SemaphoreDestroyed", + "isPrintf": true + }, + { + "expr": "MemoryPoolError", + "isPrintf": true + }, + { + "expr": "MemoryPoolNew", + "isPrintf": true + }, + { + "expr": "MemoryPoolCreated", + "isPrintf": true + }, + { + "expr": "MemoryPoolGetName", + "isPrintf": true + }, + { + "expr": "MemoryPoolAlloc", + "isPrintf": true + }, + { + "expr": "MemoryPoolAllocPending", + "isPrintf": true + }, + { + "expr": "MemoryPoolAllocTimeout", + "isPrintf": true + }, + { + "expr": "MemoryPoolAllocated", + "isPrintf": true + }, + { + "expr": "MemoryPoolAllocFailed", + "isPrintf": true + }, + { + "expr": "MemoryPoolFree", + "isPrintf": true + }, + { + "expr": "MemoryPoolDeallocated", + "isPrintf": true + }, + { + "expr": "MemoryPoolFreeFailed", + "isPrintf": true + }, + { + "expr": "MemoryPoolGetCapacity", + "isPrintf": true + }, + { + "expr": "MemoryPoolGetBlockSize", + "isPrintf": true + }, + { + "expr": "MemoryPoolGetCount", + "isPrintf": true + }, + { + "expr": "MemoryPoolGetSpace", + "isPrintf": true + }, + { + "expr": "MemoryPoolDelete", + "isPrintf": true + }, + { + "expr": "MemoryPoolDestroyed", + "isPrintf": true + }, + { + "expr": "MessageQueueError", + "isPrintf": true + }, + { + "expr": "MessageQueueNew", + "isPrintf": true + }, + { + "expr": "MessageQueueCreated", + "isPrintf": true + }, + { + "expr": "MessageQueueGetName", + "isPrintf": true + }, + { + "expr": "MessageQueuePut", + "isPrintf": true + }, + { + "expr": "MessageQueuePutPending", + "isPrintf": true + }, + { + "expr": "MessageQueuePutTimeout", + "isPrintf": true + }, + { + "expr": "MessageQueueInsertPending", + "isPrintf": true + }, + { + "expr": "MessageQueueInserted", + "isPrintf": true + }, + { + "expr": "MessageQueueNotInserted", + "isPrintf": true + }, + { + "expr": "MessageQueueGet", + "isPrintf": true + }, + { + "expr": "MessageQueueGetPending", + "isPrintf": true + }, + { + "expr": "MessageQueueGetTimeout", + "isPrintf": true + }, + { + "expr": "MessageQueueRetrieved", + "isPrintf": true + }, + { + "expr": "MessageQueueNotRetrieved", + "isPrintf": true + }, + { + "expr": "MessageQueueGetCapacity", + "isPrintf": true + }, + { + "expr": "MessageQueueGetMsgSize", + "isPrintf": true + }, + { + "expr": "MessageQueueGetCount", + "isPrintf": true + }, + { + "expr": "MessageQueueGetSpace", + "isPrintf": true + }, + { + "expr": "MessageQueueReset", + "isPrintf": true + }, + { + "expr": "MessageQueueResetDone", + "isPrintf": true + }, + { + "expr": "MessageQueueDelete", + "isPrintf": true + }, + { + "expr": "MessageQueueDestroyed", + "isPrintf": true + }, + { + "expr": "0xF000 + 0x00", + "isPrintf": false + }, + { + "expr": "%S[val2]", + "isPrintf": true + }, + { + "expr": "%N[val2]", + "isPrintf": true + }, + { + "expr": "val1", + "isPrintf": false + }, + { + "expr": "v8m_ns = __Symbol_exists(\"TZ_InitContextSystem_S\");", + "isPrintf": false + }, + { + "expr": "os_Config.stack_check = (os_Config.flags >> 1) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.stack_wmark = (os_Config.flags >> 2) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.safety_feat = (os_Config.flags >> 3) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.safety_class = (os_Config.flags >> 4) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.exec_zone = (os_Config.flags >> 5) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.watchdog = (os_Config.flags >> 6) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.obj_check = (os_Config.flags >> 7) & 1;", + "isPrintf": false + }, + { + "expr": "os_Config.svc_check = (os_Config.flags >> 8) & 1;", + "isPrintf": false + }, + { + "expr": "RTX_En = 1;", + "isPrintf": false + }, + { + "expr": "V_Major = os_Info.version / 10000000;", + "isPrintf": false + }, + { + "expr": "V_Minor = (os_Info.version / 10000) % 1000;", + "isPrintf": false + }, + { + "expr": "V_Patch = os_Info.version % 10000;", + "isPrintf": false + }, + { + "expr": "StaticMp_En = 1;", + "isPrintf": false + }, + { + "expr": "TCB_Rd = cb_Sections.thread_cb_end - cb_Sections.thread_cb_start;", + "isPrintf": false + }, + { + "expr": "CCB_Rd = cb_Sections.timer_cb_end - cb_Sections.timer_cb_start;", + "isPrintf": false + }, + { + "expr": "ECB_Rd = cb_Sections.evflags_cb_end - cb_Sections.evflags_cb_start;", + "isPrintf": false + }, + { + "expr": "MCB_Rd = cb_Sections.mutex_cb_end - cb_Sections.mutex_cb_start;", + "isPrintf": false + }, + { + "expr": "SCB_Rd = cb_Sections.semaphore_cb_end - cb_Sections.semaphore_cb_start;", + "isPrintf": false + }, + { + "expr": "PCB_Rd = cb_Sections.mempool_cb_end - cb_Sections.mempool_cb_start;", + "isPrintf": false + }, + { + "expr": "QCB_Rd = cb_Sections.msgqueue_cb_end - cb_Sections.msgqueue_cb_start;", + "isPrintf": false + }, + { + "expr": "TCB_Rd /= 80;", + "isPrintf": false + }, + { + "expr": "CCB_Rd /= 32;", + "isPrintf": false + }, + { + "expr": "ECB_Rd /= 16;", + "isPrintf": false + }, + { + "expr": "MCB_Rd /= 28;", + "isPrintf": false + }, + { + "expr": "SCB_Rd /= 16;", + "isPrintf": false + }, + { + "expr": "PCB_Rd /= 36;", + "isPrintf": false + }, + { + "expr": "QCB_Rd /= 52;", + "isPrintf": false + }, + { + "expr": "mem_head_stack.max_used = mem_list_stack[mem_list_stack._count-1].len;", + "isPrintf": false + }, + { + "expr": "mem_head_mp_data.max_used = mem_list_mp_data[mem_list_mp_data._count-1].len;", + "isPrintf": false + }, + { + "expr": "mem_head_mq_data.max_used = mem_list_mq_data[mem_list_mq_data._count-1].len;", + "isPrintf": false + }, + { + "expr": "mem_head_com.max_used = mem_list_com[mem_list_com._count-1].len;", + "isPrintf": false + }, + { + "expr": "addr = mem_list_com[i]._addr;", + "isPrintf": false + }, + { + "expr": "addr += 8;", + "isPrintf": false + }, + { + "expr": "TCB[i].cb_valid = (TCB[i].id == 0xF1) && (TCB[i].state != 0) && (TCB[i].sp != 0);", + "isPrintf": false + }, + { + "expr": "TCB[i].sp_valid = 1;", + "isPrintf": false + }, + { + "expr": "os_Info.robin_tick = TCB[i].delay;", + "isPrintf": false + }, + { + "expr": "ipsr = __GetRegVal(\"XPSR\") & 0x01FF;", + "isPrintf": false + }, + { + "expr": "psp = (v8m_ns == 0) ? (__GetRegVal(\"PSP\")) : (__GetRegVal(\"PSP_NS\"));", + "isPrintf": false + }, + { + "expr": "psp = (psp == 0) ? (TCB[i].sp) : (psp);", + "isPrintf": false + }, + { + "expr": "sp = ((ipsr != 0) && (ipsr < 16)) ? (TCB[i].sp) : (psp);", + "isPrintf": false + }, + { + "expr": "TCB[i].sp_valid = ((ipsr != 0) && (ipsr < 16)) ? (0) : (1);", + "isPrintf": false + }, + { + "expr": "sp = TCB[i].sp;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_cur = sp;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_curb = TCB[i].stack_mem + TCB[i].stack_size;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_curb -= sp;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_curp = TCB[i].stack_curb;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_curp *= 100;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_curp /= TCB[i].stack_size;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_over = (sp <= TCB[i].stack_mem) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_val = __CalcMemUsed (TCB[i].stack_mem, sp - TCB[i].stack_mem, 0xCCCCCCCC, 0xE25A2EA5);", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_over = TCB[i].stack_val >> 31;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxb = (TCB[i].stack_mem + TCB[i].stack_size) - sp;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxb += TCB[i].stack_val & 0xFFFFF;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxp = (TCB[i].stack_maxb * 100)/ TCB[i].stack_size;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxb = TCB[i].stack_size;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxp = 100;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxb = TCB[i].stack_curb;", + "isPrintf": false + }, + { + "expr": "TCB[i].stack_maxp = TCB[i].stack_curp;", + "isPrintf": false + }, + { + "expr": "TCB[i].ex_delay = TCB[i].delay;", + "isPrintf": false + }, + { + "expr": "TCB[i].ex_delay += TDL[j].delay;", + "isPrintf": false + }, + { + "expr": "k = 0;", + "isPrintf": false + }, + { + "expr": "TCB[i].wd_tick += WDL[j].wdog_tick;", + "isPrintf": false + }, + { + "expr": "TCB[i].wd_state = k;", + "isPrintf": false + }, + { + "expr": "CCB[i].cb_valid = (CCB[i].id == 0xF2) && (CCB[i].state != 0);", + "isPrintf": false + }, + { + "expr": "CCB[i].ex_tick = CCB[i].tick;", + "isPrintf": false + }, + { + "expr": "CCB[i].ex_tick += TEL[j].tick;", + "isPrintf": false + }, + { + "expr": "ECB[i].cb_valid = (ECB[i].id == 0xF3);", + "isPrintf": false + }, + { + "expr": "ECB[i].wl_idx = k;", + "isPrintf": false + }, + { + "expr": "ECB[i].wl_cnt = 0;", + "isPrintf": false + }, + { + "expr": "ECB[i].wl_cnt = (EWL._count - k);", + "isPrintf": false + }, + { + "expr": "k = EWL._count;", + "isPrintf": false + }, + { + "expr": "MCB[i].cb_valid = (MCB[i].id == 0xF5);", + "isPrintf": false + }, + { + "expr": "MCB[i].wl_idx = k;", + "isPrintf": false + }, + { + "expr": "MCB[i].wl_cnt = 0;", + "isPrintf": false + }, + { + "expr": "MCB[i].wl_cnt = (MWL._count - k);", + "isPrintf": false + }, + { + "expr": "k = MWL._count;", + "isPrintf": false + }, + { + "expr": "SCB[i].cb_valid = (SCB[i].id == 0xF6);", + "isPrintf": false + }, + { + "expr": "SCB[i].wl_idx = k;", + "isPrintf": false + }, + { + "expr": "SCB[i].wl_cnt = 0;", + "isPrintf": false + }, + { + "expr": "SCB[i].wl_cnt = (SWL._count - k);", + "isPrintf": false + }, + { + "expr": "k = SWL._count;", + "isPrintf": false + }, + { + "expr": "PCB[i].cb_valid = (PCB[i].id == 0xF7);", + "isPrintf": false + }, + { + "expr": "PCB[i].wl_idx = k;", + "isPrintf": false + }, + { + "expr": "PCB[i].wl_cnt = 0;", + "isPrintf": false + }, + { + "expr": "PCB[i].wl_cnt = (PWL._count - k);", + "isPrintf": false + }, + { + "expr": "k = PWL._count;", + "isPrintf": false + }, + { + "expr": "k = 0; j = 0;", + "isPrintf": false + }, + { + "expr": "QCB[i].cb_valid = (QCB[i].id == 0xFA);", + "isPrintf": false + }, + { + "expr": "QCB[i].wl_idx = k;", + "isPrintf": false + }, + { + "expr": "QCB[i].wl_cnt = 0;", + "isPrintf": false + }, + { + "expr": "QCB[i].wl_cnt = (QWL._count - k);", + "isPrintf": false + }, + { + "expr": "k = QWL._count;", + "isPrintf": false + }, + { + "expr": "QCB[i].ml_idx = j;", + "isPrintf": false + }, + { + "expr": "QCB[i].ml_cnt = (QML._count - j)", + "isPrintf": false + }, + { + "expr": "j = QML._count;", + "isPrintf": false + }, + { + "expr": "QML[i].addr = QML[i]._addr;", + "isPrintf": false + }, + { + "expr": "QML[i].addr += 12;", + "isPrintf": false + }, + { + "expr": "MUC_Thread_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_Timer_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_EventFlags_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_Mutex_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_Semaphore_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_MemPool_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_MsgQueue_En = 1;", + "isPrintf": false + }, + { + "expr": "MUC_En = MUC_Thread_En | MUC_Timer_En | MUC_EventFlags_En | MUC_Mutex_En | MUC_Semaphore_En | MUC_MemPool_En | MUC_MsgQueue_En;", + "isPrintf": false + }, + { + "expr": "TCB_En += TCB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "TCB[i].out_type = 0;", + "isPrintf": false + }, + { + "expr": "TCB[i].out_type += 4;", + "isPrintf": false + }, + { + "expr": "CCB_En += CCB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "SCB_En += SCB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "MCB_En += MCB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "ECB_En += ECB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "PCB_En += PCB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "QCB_En += QCB[i].cb_valid;", + "isPrintf": false + }, + { + "expr": "eth0.MacAddr", + "isPrintf": false + }, + { + "expr": "eth0.localm", + "isPrintf": false + }, + { + "expr": "eth0.localm6", + "isPrintf": false + }, + { + "expr": "eth1.MacAddr", + "isPrintf": false + }, + { + "expr": "eth1.localm", + "isPrintf": false + }, + { + "expr": "eth1.localm6", + "isPrintf": false + }, + { + "expr": "wifi0.MacAddr", + "isPrintf": false + }, + { + "expr": "wifi0.localm", + "isPrintf": false + }, + { + "expr": "wifi0.localm6", + "isPrintf": false + }, + { + "expr": "wifi1.MacAddr", + "isPrintf": false + }, + { + "expr": "wifi1.localm", + "isPrintf": false + }, + { + "expr": "wifi1.localm6", + "isPrintf": false + }, + { + "expr": "ppp0.localm", + "isPrintf": false + }, + { + "expr": "slip0.localm", + "isPrintf": false + }, + { + "expr": "ucfg.Scb", + "isPrintf": false + }, + { + "expr": "tcfg.Scb", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_dbg_proc\") || __Symbol_exists(\"net_evr_init\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_ip6_init\")", + "isPrintf": false + }, + { + "expr": "dual_stack", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_eth0_if_config\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_eth1_if_config\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_wifi0_if_config\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_wifi1_if_config\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_ppp0_if_config\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_slip0_if_config\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_udp_config\")", + "isPrintf": false + }, + { + "expr": "udp_en ? ucfg.NumSocks : 0", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"net_tcp_config\") * (dual_stack ? 2 : 1)", + "isPrintf": false + }, + { + "expr": "tcp_en ? tcfg.NumSocks : 0", + "isPrintf": false + }, + { + "expr": "%E[net_lib], ver: %d[net_ver.b3].%d[net_ver.b2].%d[net_ver.b0]", + "isPrintf": true + }, + { + "expr": "Available: %d[__size_of(\"net_if_list_all\")-1]", + "isPrintf": true + }, + { + "expr": "%E[eth0_ctrl.LinkState], Rx: %u[eth0_ctrl.RxCount], Tx: %u[eth0_ctrl.TxCount]", + "isPrintf": true + }, + { + "expr": "%M[eth0_mac]", + "isPrintf": true + }, + { + "expr": "Id: %d[eth0_ctrl.VlanId]", + "isPrintf": true + }, + { + "expr": "Address: %I[eth0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[eth0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[eth0_lm.NetMask]", + "isPrintf": true + }, + { + "expr": "%I[eth0_lm.DefGW]", + "isPrintf": true + }, + { + "expr": "%I[eth0_lm.PriDNS]", + "isPrintf": true + }, + { + "expr": "%I[eth0_lm.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[eth0_lm.Mtu]", + "isPrintf": true + }, + { + "expr": "Address: %J[eth0_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "Address: %J[eth0_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth0_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth0_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth0_lm6.LLAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth0_lm6.DefGW]", + "isPrintf": true + }, + { + "expr": "%J[eth0_lm6.PriDNS]", + "isPrintf": true + }, + { + "expr": "%J[eth0_lm6.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[eth0_lm6.Mtu]", + "isPrintf": true + }, + { + "expr": "%E[eth1_ctrl.LinkState], Rx: %u[eth1_ctrl.RxCount], Tx: %u[eth1_ctrl.TxCount]", + "isPrintf": true + }, + { + "expr": "%M[eth1_mac]", + "isPrintf": true + }, + { + "expr": "Id: %d[eth1_ctrl.VlanId]", + "isPrintf": true + }, + { + "expr": "Address: %I[eth1_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[eth1_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[eth1_lm.NetMask]", + "isPrintf": true + }, + { + "expr": "%I[eth1_lm.DefGW]", + "isPrintf": true + }, + { + "expr": "%I[eth1_lm.PriDNS]", + "isPrintf": true + }, + { + "expr": "%I[eth1_lm.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[eth1_lm.Mtu]", + "isPrintf": true + }, + { + "expr": "Address: %J[eth1_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "Address: %J[eth1_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth1_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth1_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth1_lm6.LLAddr]", + "isPrintf": true + }, + { + "expr": "%J[eth1_lm6.DefGW]", + "isPrintf": true + }, + { + "expr": "%J[eth1_lm6.PriDNS]", + "isPrintf": true + }, + { + "expr": "%J[eth1_lm6.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[eth1_lm6.Mtu]", + "isPrintf": true + }, + { + "expr": "%E[wifi0_ctrl.LinkState], Rx: %u[wifi0_ctrl.RxCount], Tx: %u[wifi0_ctrl.TxCount]", + "isPrintf": true + }, + { + "expr": "%M[wifi0_mac]", + "isPrintf": true + }, + { + "expr": "Address: %I[wifi0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[wifi0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[wifi0_lm.NetMask]", + "isPrintf": true + }, + { + "expr": "%I[wifi0_lm.DefGW]", + "isPrintf": true + }, + { + "expr": "%I[wifi0_lm.PriDNS]", + "isPrintf": true + }, + { + "expr": "%I[wifi0_lm.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[wifi0_lm.Mtu]", + "isPrintf": true + }, + { + "expr": "Address: %J[wifi0_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "Address: %J[wifi0_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi0_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi0_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi0_lm6.LLAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi0_lm6.DefGW]", + "isPrintf": true + }, + { + "expr": "%J[wifi0_lm6.PriDNS]", + "isPrintf": true + }, + { + "expr": "%J[wifi0_lm6.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[wifi0_lm6.Mtu]", + "isPrintf": true + }, + { + "expr": "%E[wifi1_ctrl.LinkState], Rx: %u[wifi1_ctrl.RxCount], Tx: %u[wifi1_ctrl.TxCount]", + "isPrintf": true + }, + { + "expr": "%M[wifi1_mac]", + "isPrintf": true + }, + { + "expr": "Address: %I[wifi1_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[wifi1_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[wifi1_lm.NetMask]", + "isPrintf": true + }, + { + "expr": "%I[wifi1_lm.DefGW]", + "isPrintf": true + }, + { + "expr": "%I[wifi1_lm.PriDNS]", + "isPrintf": true + }, + { + "expr": "%I[wifi1_lm.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[wifi1_lm.Mtu]", + "isPrintf": true + }, + { + "expr": "Address: %J[wifi1_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "Address: %J[wifi1_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi1_lm6.IpAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi1_lm6.TempAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi1_lm6.LLAddr]", + "isPrintf": true + }, + { + "expr": "%J[wifi1_lm6.DefGW]", + "isPrintf": true + }, + { + "expr": "%J[wifi1_lm6.PriDNS]", + "isPrintf": true + }, + { + "expr": "%J[wifi1_lm6.SecDNS]", + "isPrintf": true + }, + { + "expr": "%d[wifi1_lm6.Mtu]", + "isPrintf": true + }, + { + "expr": "%E[ppp0_ctrl.Flags & 0x1C0], Rx: %u[ppp0_ctrl.RxCount], Tx: %u[ppp0_ctrl.TxCount]", + "isPrintf": true + }, + { + "expr": "Address: %I[ppp0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[ppp0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[ppp0_lm.PriDNS]", + "isPrintf": true + }, + { + "expr": "%I[ppp0_lm.SecDNS]", + "isPrintf": true + }, + { + "expr": "%E[slip0_ctrl.Flags & 0x8], Rx: %u[slip0_ctrl.RxCount], Tx: %u[slip0_ctrl.TxCount]", + "isPrintf": true + }, + { + "expr": "Address: %I[slip0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[slip0_lm.IpAddr]", + "isPrintf": true + }, + { + "expr": "%I[slip0_lm.PriDNS]", + "isPrintf": true + }, + { + "expr": "%I[slip0_lm.SecDNS]", + "isPrintf": true + }, + { + "expr": "Used: %d[udp_used], Available: %d[udp_num]", + "isPrintf": true + }, + { + "expr": "%E[UDP[i].State], Port: %d[UDP[i].LocPort]", + "isPrintf": true + }, + { + "expr": "%d[UDP[i].LocPort]", + "isPrintf": true + }, + { + "expr": "%S[UDP[i].cb_func]", + "isPrintf": true + }, + { + "expr": "%E[UDP[i].Flags & 3]", + "isPrintf": true + }, + { + "expr": "Used: %d[tcp_used], Available: %d[tcp_num]", + "isPrintf": true + }, + { + "expr": "%E[TCP[i].State], Port: %d[TCP[i].LocPort]", + "isPrintf": true + }, + { + "expr": "%d[TCP[i].LocPort]", + "isPrintf": true + }, + { + "expr": "%S[TCP[i].cb_func]", + "isPrintf": true + }, + { + "expr": "%E[TCP[i].Type & 7]", + "isPrintf": true + }, + { + "expr": "%I[TCP[i].Addr], Port: %d[TCP[i].Port]", + "isPrintf": true + }, + { + "expr": "%J[TCP[i].Addr], Port: %d[TCP[i].Port]", + "isPrintf": true + }, + { + "expr": "%d[TCP[i].Timer] sec", + "isPrintf": true + }, + { + "expr": "%E[TCP4[i].State], Port: %d[TCP4[i].LocPort]", + "isPrintf": true + }, + { + "expr": "%d[TCP4[i].LocPort]", + "isPrintf": true + }, + { + "expr": "%S[TCP4[i].cb_func]", + "isPrintf": true + }, + { + "expr": "%E[TCP4[i].Type & 7]", + "isPrintf": true + }, + { + "expr": "%I[TCP4[i].Addr], Port: %d[TCP4[i].Port]", + "isPrintf": true + }, + { + "expr": "%d[TCP4[i].Timer] sec", + "isPrintf": true + }, + { + "expr": "ver=%d[val1>>8].%d[val1&0xFF].%d[val2]", + "isPrintf": true + }, + { + "expr": "thread=netCore_Thread", + "isPrintf": false + }, + { + "expr": "timer=net_tick", + "isPrintf": false + }, + { + "expr": "success", + "isPrintf": false + }, + { + "expr": "netif=%E[val1, NetIf:id], opt=%E[val2, Netif_Opt:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ver=%E[val2, Netif_Ver:id]", + "isPrintf": true + }, + { + "expr": "name=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "base=%x[val1], size=%d[val2] (limit_0=%d[val3], limit_1=%d[val4])", + "isPrintf": true + }, + { + "expr": "mem=%x[val1], size=%d[val2] (used=%d[val3], blocks=%d[val4])", + "isPrintf": true + }, + { + "expr": "size=%d[val1] (used=%d[val2], blocks=%d[val3])", + "isPrintf": true + }, + { + "expr": "mem=%x[val1], new_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "mem=%x[val1]", + "isPrintf": true + }, + { + "expr": "link=%x[val1]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1, NetVal:w0], mac=%M[val1, NetVal:w1]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], vlan_id=%d[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], ip=%I[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], mask=%I[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], gw=%I[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], pri_dns=%I[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], sec_dns=%I[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], mtu=%d[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], ip=%J[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], gw=%J[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], pri_dns=%J[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], sec_dns=%J[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], pref_len=%d[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], len=%d[val2, NetVal:low], ver=%E[val2>>16, IP_Ver:id]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], len=%d[val2, NetVal:low] (max=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], addr=0.0.0.0", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], dst_addr=%I[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], ca_entry=%d[val2, NetVal:high], len=%d[val2, NetVal:low]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], addr=0:0:0:0:0:0:0:0", + "isPrintf": true + }, + { + "expr": "dst_addr=%J[val1]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], ver=%d[val2]", + "isPrintf": true + }, + { + "expr": "dst=%M[val1, ETH_Header:DstAddr], src=%M[val1, ETH_Header:SrcAddr], proto=%E[val1, ETH_Header:Proto]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], link-down", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], speed=%E[val2 & 3, ETH_Speed:id], duplex=%E[val2 >> 2, ETH_Duplex:id]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], proto=%x[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1, NetVal:w0], mac=%M[val1, NetVal:w1], get only", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], max=%d[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], mode=%E[val2, WIFI_Mode:id]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], num=%d[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], opt=%E[val2, WIFI_Opt:id]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], dst_addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "if_num=%d[val1], state=%E[val2, WIFI_State:id]", + "isPrintf": true + }, + { + "expr": "ppp", + "isPrintf": false + }, + { + "expr": "netif=PPP", + "isPrintf": false + }, + { + "expr": "error", + "isPrintf": false + }, + { + "expr": "ip=%I[val1]", + "isPrintf": true + }, + { + "expr": "pri_dns=%I[val1]", + "isPrintf": true + }, + { + "expr": "sec_dns=%I[val1]", + "isPrintf": true + }, + { + "expr": "mtu=%d[val1]", + "isPrintf": true + }, + { + "expr": "proto=%E[val1, PPP_Proto:id], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "len=%d[val1] (min=%d[val2])", + "isPrintf": true + }, + { + "expr": "len=%d[val1]", + "isPrintf": true + }, + { + "expr": "ctrl=%x[val1] (valid=%x[val2])", + "isPrintf": true + }, + { + "expr": "ver=%d[val1]", + "isPrintf": true + }, + { + "expr": "len=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "proto=%E[val1, PPP_Proto:id]", + "isPrintf": true + }, + { + "expr": "code=%x[val2], proto=%E[val1, PPP_Proto:id]", + "isPrintf": true + }, + { + "expr": "dial-number=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "connected", + "isPrintf": false + }, + { + "expr": "start", + "isPrintf": false + }, + { + "expr": "stop", + "isPrintf": false + }, + { + "expr": "username=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "password=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "off-line", + "isPrintf": false + }, + { + "expr": "discard", + "isPrintf": false + }, + { + "expr": "ppp-lcp", + "isPrintf": false + }, + { + "expr": "send", + "isPrintf": false + }, + { + "expr": "retries=0", + "isPrintf": false + }, + { + "expr": "char_map=%x[val1]", + "isPrintf": true + }, + { + "expr": "enable", + "isPrintf": false + }, + { + "expr": "magic_num=%x[val1]", + "isPrintf": true + }, + { + "expr": "code=%E[val1, LCP_Header:Code], id=%x[val1, LCP_Header:Id], len=%d[val1, LCP_Header:Len]", + "isPrintf": true + }, + { + "expr": "mru=%d[val1]", + "isPrintf": true + }, + { + "expr": "auth_type=%E[val1, PPP_Auth:id]", + "isPrintf": true + }, + { + "expr": "opt=%x[val1]", + "isPrintf": true + }, + { + "expr": "id=%x[val1] (valid=%x[val2])", + "isPrintf": true + }, + { + "expr": "magic_num=%x[val1] (valid=%x[val2])", + "isPrintf": true + }, + { + "expr": "ppp-pap", + "isPrintf": false + }, + { + "expr": "peer_id=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "code=%E[val1, PAP_Header:Code], id=%x[val1, PAP_Header:Id], len=%d[val1, PAP_Header:Len]", + "isPrintf": true + }, + { + "expr": "ppp-chap", + "isPrintf": false + }, + { + "expr": "code=%E[val1, CHAP_Header:Code], id=%x[val1, CHAP_Header:Id], len=%d[val1, CHAP_Header:Len]", + "isPrintf": true + }, + { + "expr": "ppp-ipcp", + "isPrintf": false + }, + { + "expr": "ip4", + "isPrintf": false + }, + { + "expr": "code=%E[val1, IPCP_Header:Code], id=%x[val1, IPCP_Header:Id], len=%d[val1, IPCP_Header:Len]", + "isPrintf": true + }, + { + "expr": "slip", + "isPrintf": false + }, + { + "expr": "netif=SLIP", + "isPrintf": false + }, + { + "expr": "len=%d[val1], ver=%E[val2, IP_Ver:id]", + "isPrintf": true + }, + { + "expr": "loopback", + "isPrintf": false + }, + { + "expr": "default=%E[val1, NetIf:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], len=%d[val2, NetVal:low] (min=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ver=%d[val2]", + "isPrintf": true + }, + { + "expr": "dst=%I[val1, IP4_Header:DstAddr], src=%I[val1, IP4_Header:SrcAddr], proto=%E[val1, IP4_Header:Proto], id=%x[val1, IP4_Header:Id], frag=%x[val1, IP4_Header:FragOffs], len=%d[val1, IP4_Header:Len]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ip=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], proto=%E[val2, IP4_Proto:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], flags=%x[val2 & 0xE000], frag_offs=%x[val2 & 0x1FFF]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], proto=%E[val2 & 0xFF, IP4_Proto:id], len=%d[val2, NetVal:high]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], proto=%d[val2]", + "isPrintf": true + }, + { + "expr": "type=%E[val1, ICMP_Header:Type], code=%d[val1, ICMP_Header:Code], cksum=%x[val1, ICMP_Header:Chksum]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], code=%d[val2, NetVal:low] (valid=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], eid=%x[val2, NetVal:low] (valid=%x[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%x[val2]", + "isPrintf": true + }, + { + "expr": "ping", + "isPrintf": false + }, + { + "expr": "netif=%E[val1, NetIf:id], groups=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], addr=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], id=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], used=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val1 >> 16, IGMP_Type:id], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], max_time=%d[val2*100]ms", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], num=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], port=%d[val2, NetVal:low] (valid=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], tid=%x[val2, NetVal:low] (valid=%x[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], deleted=%d[val2, NetVal:low] (max=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], opt=%E[val2, DHCP_Opt:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], state=%E[val2, DHCP_State:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], next=%E[val2, DHCP_State:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], state=%E[val2 & 0xFF, DHCP_State:id], next=%E[val2 >> 16, DHCP_State:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ip=%I[val2], mask=%I[val3]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val2 & 0xFF, DHCP_Type:id], bcast=%t[val2 >> 16 ? \"Yes\" : \"No\"]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], addr=%I[val2], len=%d[val1, NetVal:high]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], xid=%x[val2] (valid=%x[val3])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], chaddr=%M[val1, NetVal:w1]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], cookie=%x[val2] (valid=%x[val3])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val2 & 0xFF, DHCP_Type:id] (valid=%E[val2 >> 16, DHCP_Type:id])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val2, DHCP_Type:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], relay=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], server_id=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], mask=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], gw=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], pri=%I[val2], sec=%I[val3]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], time=%d[val2]s", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], t1=%d[val2]s", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], t2=%d[val2]s", + "isPrintf": true + }, + { + "expr": "bootfile=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ntp=%I[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ntp=%I[val2], ntp2=%I[val3]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ntp=%I[val2], ntp2=%I[val3], ntp3=%I[val4]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], state=%E[val2, DHCP_State:Id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%d[val2, NetVal:high], len=%d[val2, NetVal:low]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entries=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], opcode=%E[val2>>16, ARP_Opcode:id], len=%d[val2, NetVal:low]", + "isPrintf": true + }, + { + "expr": "op=%E[val1, ARP_Header:OpCode], spa=%I[val1, ARP_Header:SendIp], tpa=%I[val1, ARP_Header:TargIp], sha=%M[val1, ARP_Header:SendHw], tha=%M[val1, ARP_Header:TargHw]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entry=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], mac=%M[val1, NetVal:w1]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], opcode=%x[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], opcode=%E[val2, ARP_Opcode:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ip=%I[val1, ARP_Cache:IpAddr], mac=%M[val1, ARP_Cache:MacAddr]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], mac=%M[val1, ARP_Cache:MacAddr], ip=%I[val1, ARP_Cache:IpAddr]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entry=%d[val1, ARP_Cache:Num], type=%E[val1, ARP_Cache:Type], ip=%I[val1, ARP_Cache:IpAddr], mac=%M[val1, ARP_Cache:MacAddr]", + "isPrintf": true + }, + { + "expr": "dst=%J[val1, IP6_Header:DstAddr], src=%J[val1, IP6_Header:SrcAddr], proto=%E[val1, IP6_Header:NextHdr], hops=%d[val1, IP6_Header:HopLim], len=%d[val1, IP6_Header:Len]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ip=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], proto=%E[val2, IP6_Proto:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], proto=%E[val2, IP6_Proto:id], len=%d[val2, NetVal:high]", + "isPrintf": true + }, + { + "expr": "ip6", + "isPrintf": false + }, + { + "expr": "type=%E[val1, ICMP6_Header:Type], code=%d[val1, ICMP6_Header:Code], cksum=%x[val1, ICMP6_Header:Chksum]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val2, ICMP6_Type:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], hop_limit=%d[val2, NetVal:low] (valid=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "ip=%J[val1]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], opt=%E[val2, DHCP6_Opt:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], mode=%E[val2, DHCP6_Mode:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], state=%E[val2, DHCP6_State:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], next=%E[val2, DHCP6_State:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], next=%E[val2, DHCP6_State:id], delay=%d[val2, NetVal:high]00ms", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], state=%E[val2, DHCP6_State:id], next=%E[val2>>16, DHCP6_State:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val1>>16, DHCP6_Type:id], xid=%x[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val2, DHCP6_Type:id] (valid=%E[val2>>16, DHCP6_Type:id])", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], type=%E[val2, DHCP6_Type:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], status=%E[val2, DHCP6_Status:id]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], pri=%J[val1, DHCP6_DNS:PriDNS], sec=%J[val1, DHCP6_DNS:SecDNS]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], iaid=%x[val2] (valid=%x[val3])", + "isPrintf": true + }, + { + "expr": "addr=%J[val1, DHCP6_IANA:Addr], valid=%d[val1, DHCP6_IANA:ValidTime]s, prefrd=%d[val1, DHCP6_IANA:PrefTime]s, t1=%d[val1, DHCP6_IANA:T1]s, t2=%d[val1, DHCP6_IANA:T2]s", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], src_addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], hop_limit=%d[val2, NetVal:b2], flags=%x[val2, NetVal:b3], lifetime=%d[val2, NetVal:low]s", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], mtu=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], addr=%M[val1, NetVal:w1]", + "isPrintf": true + }, + { + "expr": "prefix=%J[val1, NDP_Prefix:Addr]/%d[val1, NDP_Prefix:PrefLen], valid=%d[val1, NDP_Prefix:ValTime]s, prefrd=%d[val1, NDP_Prefix:PrefTime]s, flags=%x[val1, NDP_Prefix:Flags]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], targ_addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], dst_addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], flags=%x[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], local_addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], temp_addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ip=%J[val1, NDP_Cache:IpAddr], mac=%M[val1, NDP_Cache:MacAddr], type=%E[val1, NDP_Cache:Type]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], max_count=%d[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entry=%d[val1, NetVal:high], tout=%d[val2]s", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entry=%d[val1, NDP_Cache:Num], ip=%J[val1, NDP_Cache:IpAddr], type=%E[val1, NDP_Cache:Type]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entry=%d[val1, NDP_Cache:Num], ip=%J[val1, NDP_Cache:IpAddr]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], gw=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], ip=%J[val1, NDP_Cache:IpAddr], mac=%M[val1, NDP_Cache:MacAddr]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], mac=%M[val1, NDP_Cache:MacAddr], ip=%J[val1, NDP_Cache:IpAddr]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], entry=%d[val1, NDP_Cache:Num], type=%E[val1, NDP_Cache:Type], ip=%J[val1, NDP_Cache:IpAddr], mac=%M[val1, NDP_Cache:MacAddr]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], addr=%J[val2]", + "isPrintf": true + }, + { + "expr": "netif=%E[val1, NetIf:id], max_time=%d[val2]ms", + "isPrintf": true + }, + { + "expr": "sockets=%d[val1]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "used=%d[val1]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], state=%E[val2, UDP_State:id]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], port=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], loc_port=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], opt=%E[val2 & 0xFF, UDP_Opt:id], val=%d[val2 >> 8]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], tos=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], ttl=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], tclass=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], hop_limit=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], send=%t[val2 & 1 ? \"On\" : \"Off\"], verify=%t[val2 & 2 ? \"On\" : \"Off\"]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], netif=%E[val2, NetIf:id]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], opt=%E[val2, UDP_Opt:id]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], value=%d[val2]", + "isPrintf": true + }, + { + "expr": "size=%d[val1]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], new_len=%d[val2]", + "isPrintf": true + }, + { + "expr": "dst_port=%d[val1, UDP_Header:DstPort], src_port=%d[val1, UDP_Header:SrcPort], cksum=%x[val1, UDP_Header:Chksum], len=%d[val1, UDP_Header:Len]", + "isPrintf": true + }, + { + "expr": "udp", + "isPrintf": false + }, + { + "expr": "sockets=%d[val1], mss=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], state=%E[val2, TCP_State:id]", + "isPrintf": true + }, + { + "expr": "ip=%I[val1, NetAddr:addr], port=%d[val1, NetAddr:port]", + "isPrintf": true + }, + { + "expr": "ip=%J[val1, NetAddr:addr], port=%d[val1, NetAddr:port]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], rto=%d[((val2 >> 19) + (val2 & 0xFFFF))*100]ms, sa=%d[val2 >> 16], sv=%d[val2 & 0xFFFF]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], window_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], opt=%E[val2 & 0xFF, TCP_Opt:id], val=%d[val2 >> 8]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], tclasst=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], tout=%d[val2]s", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], enable=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], opt=%E[val2, TCP_Opt:id]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], cwnd=%d[val2 >> 16], ssth=%d[val2 & 0xFFFF]", + "isPrintf": true + }, + { + "expr": "dport=%d[val1, TCP_Header:DstPort], sport=%d[val1, TCP_Header:SrcPort], seq=%x[val1, TCP_Header:SeqNr], ack=%x[val1, TCP_Header:AckNr], flags=%E[val1, TCP_Header:Flags], win=%d[val1, TCP_Header:Window], cksum=%x[val1, TCP_Header:Chksum]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], send_win=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], rec_win=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], next=%E[val2, TCP_State:id]", + "isPrintf": true + }, + { + "expr": "rst", + "isPrintf": false + }, + { + "expr": "socket=%d[val1], opt_len=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], mss=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], dup_acks=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], ack_len=%d[val2]", + "isPrintf": true + }, + { + "expr": "socket=%d[val1], len=%d[val2 >> 16], tout=%d[(val2 & 0xFFFF)*100]ms", + "isPrintf": true + }, + { + "expr": "tcp", + "isPrintf": false + }, + { + "expr": "sock=%d[val1], type=%E[val2, BSD_Type:id]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], backlog=%d[val2]", + "isPrintf": true + }, + { + "expr": "child_sock=%d[val1]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], num_sent=%d[val2 >> 16], len=%d[val2 & 0xFFFF]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], num_sent=%d[val2]", + "isPrintf": true + }, + { + "expr": "nfds=%d[val1]", + "isPrintf": true + }, + { + "expr": "n_ready=%d[val1]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], level=%E[val2 >> 4, BSD_Level:id], name=%E[val2, BSD_Optname:id]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], enable=%d[val2]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], tout=%d[val2]ms", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], netif=%E[val2, NetIf:id]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], tos=%d[val2]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], ttl=%d[val2]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], tclass=%d[val2]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], hop_limit=%d[val2]", + "isPrintf": true + }, + { + "expr": "sock=%d[val1], event=%E[val2, TCP_Event:id]", + "isPrintf": true + }, + { + "expr": "bsd", + "isPrintf": false + }, + { + "expr": "dns", + "isPrintf": false + }, + { + "expr": "sessions=%d[val1], port=%d[val2]", + "isPrintf": true + }, + { + "expr": "root=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "session=%d[val1]", + "isPrintf": true + }, + { + "expr": "path=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "port=%d[val1]", + "isPrintf": true + }, + { + "expr": "\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "session=%d[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], len=%d[val2, NetVal:low] (min=%d[val2, NetVal:high])", + "isPrintf": true + }, + { + "expr": "session=%d[val1], method=%E[val2, HTTP_Method:id]", + "isPrintf": true + }, + { + "expr": "%E[val1, HTTP_Drive:id]", + "isPrintf": true + }, + { + "expr": "size=%d[val1], lm_time=%d[val2]", + "isPrintf": true + }, + { + "expr": "url=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "user=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "lang=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "xml_type=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "new_len=%d[val1]", + "isPrintf": true + }, + { + "expr": "file=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "http", + "isPrintf": false + }, + { + "expr": "sessions=%d[val1], port=%d[val2, NetVal:low], tout=%d[val2, NetVal:high]s", + "isPrintf": true + }, + { + "expr": "command=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "session=%d[val1], sys=Windows_NT", + "isPrintf": true + }, + { + "expr": "session=%d[val1], mode=%E[val2, FTP_DataMode:id]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], port=%d[val2]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], local_port=%d[val2]", + "isPrintf": true + }, + { + "expr": "new_name=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "session=%d[val1], size=%u[val2]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], lm_time=\"%t[val2]\"", + "isPrintf": true + }, + { + "expr": "ftp", + "isPrintf": false + }, + { + "expr": "mode=%E[val1, FTPc_Mode:id]", + "isPrintf": true + }, + { + "expr": "reply=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "commandd=%E[val1, FTPc_Cmd:id]", + "isPrintf": true + }, + { + "expr": "local_port=%d[val1]", + "isPrintf": true + }, + { + "expr": "cb_event=%E[val1, FTPc_Event:id]", + "isPrintf": true + }, + { + "expr": "line_buff=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "session=%d[val1], num_char=%d[val2]", + "isPrintf": true + }, + { + "expr": "telnet", + "isPrintf": false + }, + { + "expr": "sessions=%d[val1], port=%d[val2, NetVal:low], firewall_en=%d[val2, NetVal:high]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], block=%d[val2, NetVal:high], len=%d[val2, NetVal:low]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], opcode=%E[val2, TFTP_Opcode:id]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], code=%E[val2, TFTP_Error:id]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], size=%d[val2]", + "isPrintf": true + }, + { + "expr": "session=%d[val1], block=%d[val2]", + "isPrintf": true + }, + { + "expr": "mode=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "session=%d[val1], block_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "tftp", + "isPrintf": false + }, + { + "expr": "state=%E[val1, TFTPc_State:id]", + "isPrintf": true + }, + { + "expr": "block=%d[val1]", + "isPrintf": true + }, + { + "expr": "block=%d[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "ip=%I[val1, NetAddr:addr]", + "isPrintf": true + }, + { + "expr": "ip=%J[val1, NetAddr:addr]", + "isPrintf": true + }, + { + "expr": "tid=%d[val1]", + "isPrintf": true + }, + { + "expr": "code=%E[val1, TFTP_Error:id]", + "isPrintf": true + }, + { + "expr": "opcode=%E[val1, TFTP_Opcode:id]", + "isPrintf": true + }, + { + "expr": "smtp", + "isPrintf": false + }, + { + "expr": "server=%I[val1, NetAddr:addr], port=%d[val1, NetAddr:port]", + "isPrintf": true + }, + { + "expr": "server=%J[val1, NetAddr:addr], port=%d[val1, NetAddr:port]", + "isPrintf": true + }, + { + "expr": "recipients=%d[val1]", + "isPrintf": true + }, + { + "expr": "attachments=%d[val1]", + "isPrintf": true + }, + { + "expr": "auth_mode=%E[val1, SMTP_Auth:id]", + "isPrintf": true + }, + { + "expr": "response=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "CRLF.CRLF", + "isPrintf": false + }, + { + "expr": "cb_event=%E[val1, SMTPc_Event:id]", + "isPrintf": true + }, + { + "expr": "tls_id=%d[val1]", + "isPrintf": true + }, + { + "expr": "entries=%d[val1]", + "isPrintf": true + }, + { + "expr": "dns=%E[val1, DNS_Active:id]", + "isPrintf": true + }, + { + "expr": "ip=%I[val1, NetAddr:addr], len=%d[val1, NetAddr:port]", + "isPrintf": true + }, + { + "expr": "ip=%J[val1, NetAddr:addr], len=%d[val1, NetAddr:port]", + "isPrintf": true + }, + { + "expr": "tid=%x[val1, DNS_Header:TID], flags=%x[val1, DNS_Header:Flags], qd=%d[val1, DNS_Header:QDCOUNT], an=%d[val1, DNS_Header:ANCOUNT], ns=%d[val1, DNS_Header:NSCOUNT], ar=%d[val1, DNS_Header:ARCOUNT]", + "isPrintf": true + }, + { + "expr": "tid=%x[val1] (valid=%x[val2])", + "isPrintf": true + }, + { + "expr": "ip=%I[val1, NetAddr:addr], ttl=%d[val1 >> 1]", + "isPrintf": true + }, + { + "expr": "ip=%J[val1, NetAddr:addr], ttl=%d[val1 >> 1]", + "isPrintf": true + }, + { + "expr": "rr_type=%E[val1, DNS_RR:type]", + "isPrintf": true + }, + { + "expr": "deleted=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "snmp", + "isPrintf": false + }, + { + "expr": "community=\"%t[val1]\"", + "isPrintf": true + }, + { + "expr": "ip=%I[val1, SNMP_Trap:addr], generic=%E[val1, SNMP_Trap:generic], specific=%d[val1, SNMP_Trap:specific], nobj=%d[val1, SNMP_Trap:nobj]", + "isPrintf": true + }, + { + "expr": "generic=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "nobj=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "obj=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "size=%d[val1] (max=%d[val2])", + "isPrintf": true + }, + { + "expr": "type=%E[val2, SNMP_Object:type], index=%d[val1]", + "isPrintf": true + }, + { + "expr": "oid=%t[val1]", + "isPrintf": true + }, + { + "expr": "val=%d[val2]", + "isPrintf": true + }, + { + "expr": "val=%u[val2]", + "isPrintf": true + }, + { + "expr": "ip=%I[val2]", + "isPrintf": true + }, + { + "expr": "oid=%t[val2]", + "isPrintf": true + }, + { + "expr": "len=%d[val1>>16], val=\"%t[val2]\"", + "isPrintf": true + }, + { + "expr": "ip=%I[val2], len=%d[val1]", + "isPrintf": true + }, + { + "expr": "type=%E[val1, SNMP_PDU:type]", + "isPrintf": true + }, + { + "expr": "type=%E[val1, SNMP_PDU:type], id=%d[val2]", + "isPrintf": true + }, + { + "expr": "obj=%d[val1]", + "isPrintf": true + }, + { + "expr": "error=%E[val1, SNMP_Error:id], index=%d[val2]", + "isPrintf": true + }, + { + "expr": "id=%d[val1]", + "isPrintf": true + }, + { + "expr": "mode=%E[val1, SNTP_Mode:id]", + "isPrintf": true + }, + { + "expr": "state=%E[val1, SNTP_State:id]", + "isPrintf": true + }, + { + "expr": "ntp=%I[val1]", + "isPrintf": true + }, + { + "expr": "ntp=%I[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "mode=%d[val1]", + "isPrintf": true + }, + { + "expr": "ref=%x[val1], utc=%d[val2]", + "isPrintf": true + }, + { + "expr": "ref=%x[val1]", + "isPrintf": true + }, + { + "expr": "sntp", + "isPrintf": false + }, + { + "expr": "eth0_en", + "isPrintf": false + }, + { + "expr": "eth0_en & 2", + "isPrintf": false + }, + { + "expr": "eth1_en", + "isPrintf": false + }, + { + "expr": "eth1_en & 2", + "isPrintf": false + }, + { + "expr": "wifi0_en", + "isPrintf": false + }, + { + "expr": "wifi0_en & 2", + "isPrintf": false + }, + { + "expr": "wifi1_en", + "isPrintf": false + }, + { + "expr": "wifi1_en & 2", + "isPrintf": false + }, + { + "expr": "ppp0_en", + "isPrintf": false + }, + { + "expr": "slip0_en", + "isPrintf": false + }, + { + "expr": "udp_en", + "isPrintf": false + }, + { + "expr": "tcp_en", + "isPrintf": false + }, + { + "expr": "tcp_en & 2", + "isPrintf": false + }, + { + "expr": "UDP[i].State", + "isPrintf": false + }, + { + "expr": "TCP[i].State", + "isPrintf": false + }, + { + "expr": "TCP4[i].State", + "isPrintf": false + }, + { + "expr": "!debug", + "isPrintf": false + }, + { + "expr": "debug", + "isPrintf": false + }, + { + "expr": "eth0_ctrl.VlanId", + "isPrintf": false + }, + { + "expr": "!eth0_ctrl.VlanId", + "isPrintf": false + }, + { + "expr": "eth0_lm.Mtu", + "isPrintf": false + }, + { + "expr": "!eth0_lm.Mtu", + "isPrintf": false + }, + { + "expr": "__CalcMemUsed(eth0_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "!__CalcMemUsed(eth0_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "eth0_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "!eth0_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "eth1_ctrl.VlanId", + "isPrintf": false + }, + { + "expr": "!eth1_ctrl.VlanId", + "isPrintf": false + }, + { + "expr": "eth1_lm.Mtu", + "isPrintf": false + }, + { + "expr": "!eth1_lm.Mtu", + "isPrintf": false + }, + { + "expr": "__CalcMemUsed(eth1_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "!__CalcMemUsed(eth1_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "eth1_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "!eth1_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "wifi0_lm.Mtu", + "isPrintf": false + }, + { + "expr": "!wifi0_lm.Mtu", + "isPrintf": false + }, + { + "expr": "__CalcMemUsed(wifi0_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "!__CalcMemUsed(wifi0_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "wifi0_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "!wifi0_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "wifi1_lm.Mtu", + "isPrintf": false + }, + { + "expr": "!wifi1_lm.Mtu", + "isPrintf": false + }, + { + "expr": "__CalcMemUsed(wifi1_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "!__CalcMemUsed(wifi1_lm6.IpAddr, 16, 0, 0)", + "isPrintf": false + }, + { + "expr": "wifi1_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "!wifi1_lm6.Mtu", + "isPrintf": false + }, + { + "expr": "TCP[i].State > TCP_INFO:State:Listen", + "isPrintf": false + }, + { + "expr": "TCP[i].AddrType == TCP_INFO:AddrType:IP4", + "isPrintf": false + }, + { + "expr": "TCP[i].AddrType == TCP_INFO:AddrType:IP6", + "isPrintf": false + }, + { + "expr": "!dual_stack", + "isPrintf": false + }, + { + "expr": "TCP4[i].State > TCP_INFO4:State:Closed", + "isPrintf": false + }, + { + "expr": "TCP4[i].State > TCP_INFO4:State:Listen", + "isPrintf": false + }, + { + "expr": "(val1 >> 18 == 1)", + "isPrintf": false + }, + { + "expr": "!(val1 & 0x1)", + "isPrintf": false + }, + { + "expr": "(val1 & 0x1)", + "isPrintf": false + }, + { + "expr": "(val1 == 0)", + "isPrintf": false + }, + { + "expr": "((val1 & 0xFF)==0)", + "isPrintf": false + }, + { + "expr": "net_lib_version", + "isPrintf": false + }, + { + "expr": "net_eth0_if_config", + "isPrintf": false + }, + { + "expr": "net_eth0_if_control", + "isPrintf": false + }, + { + "expr": "net_eth1_if_config", + "isPrintf": false + }, + { + "expr": "net_eth1_if_control", + "isPrintf": false + }, + { + "expr": "net_wifi0_if_config", + "isPrintf": false + }, + { + "expr": "net_wifi0_if_control", + "isPrintf": false + }, + { + "expr": "net_wifi1_if_config", + "isPrintf": false + }, + { + "expr": "net_wifi1_if_control", + "isPrintf": false + }, + { + "expr": "net_ppp0_if_config", + "isPrintf": false + }, + { + "expr": "net_ppp0_if_control", + "isPrintf": false + }, + { + "expr": "net_slip0_if_config", + "isPrintf": false + }, + { + "expr": "net_slip0_if_control", + "isPrintf": false + }, + { + "expr": "net_udp_config", + "isPrintf": false + }, + { + "expr": "net_tcp_config", + "isPrintf": false + }, + { + "expr": "udp_num", + "isPrintf": false + }, + { + "expr": "tcp_num", + "isPrintf": false + }, + { + "expr": "Release Library", + "isPrintf": true + }, + { + "expr": "Debug Library", + "isPrintf": true + }, + { + "expr": "Interfaces", + "isPrintf": true + }, + { + "expr": "%E[eth0.Id]", + "isPrintf": true + }, + { + "expr": "MAC address", + "isPrintf": true + }, + { + "expr": "VLAN", + "isPrintf": true + }, + { + "expr": "IPv4 settings", + "isPrintf": true + }, + { + "expr": "IP address", + "isPrintf": true + }, + { + "expr": "Network mask", + "isPrintf": true + }, + { + "expr": "Default gateway", + "isPrintf": true + }, + { + "expr": "Primary DNS server", + "isPrintf": true + }, + { + "expr": "Secondary DNS server", + "isPrintf": true + }, + { + "expr": "MTU size", + "isPrintf": true + }, + { + "expr": "IP Fragmentation", + "isPrintf": true + }, + { + "expr": "IPv6 settings", + "isPrintf": true + }, + { + "expr": "Temporary address", + "isPrintf": true + }, + { + "expr": "Link-local address", + "isPrintf": true + }, + { + "expr": "%E[eth1.Id]", + "isPrintf": true + }, + { + "expr": "%E[wifi0.Id]", + "isPrintf": true + }, + { + "expr": "%E[wifi1.Id]", + "isPrintf": true + }, + { + "expr": "%E[ppp0.Id]", + "isPrintf": true + }, + { + "expr": "%E[slip0.Id]", + "isPrintf": true + }, + { + "expr": "UDP sockets", + "isPrintf": true + }, + { + "expr": "Socket %d[i+1]", + "isPrintf": true + }, + { + "expr": "Local Port", + "isPrintf": true + }, + { + "expr": "Callback Function", + "isPrintf": true + }, + { + "expr": "Checksum Options", + "isPrintf": true + }, + { + "expr": "TCP sockets", + "isPrintf": true + }, + { + "expr": "Mode Options", + "isPrintf": true + }, + { + "expr": "Peer Address", + "isPrintf": true + }, + { + "expr": "Timeout", + "isPrintf": true + }, + { + "expr": "InitSystem", + "isPrintf": true + }, + { + "expr": "ThreadCreateFailed", + "isPrintf": true + }, + { + "expr": "TimerCreateFailed", + "isPrintf": true + }, + { + "expr": "InitComplete", + "isPrintf": true + }, + { + "expr": "GetOption", + "isPrintf": true + }, + { + "expr": "SetOption", + "isPrintf": true + }, + { + "expr": "SetDefault", + "isPrintf": true + }, + { + "expr": "SetHostName", + "isPrintf": true + }, + { + "expr": "UnInitSystem", + "isPrintf": true + }, + { + "expr": "UnInitComplete", + "isPrintf": true + }, + { + "expr": "InitMemory", + "isPrintf": true + }, + { + "expr": "AllocMemory", + "isPrintf": true + }, + { + "expr": "AllocLimitExceeded", + "isPrintf": true + }, + { + "expr": "AllocOutOfMemory", + "isPrintf": true + }, + { + "expr": "ShrinkMemory", + "isPrintf": true + }, + { + "expr": "FreeMemory", + "isPrintf": true + }, + { + "expr": "FreeInvalidBlock", + "isPrintf": true + }, + { + "expr": "FreeLinkCorrupted", + "isPrintf": true + }, + { + "expr": "UnInitMemory", + "isPrintf": true + }, + { + "expr": "InitInterface", + "isPrintf": true + }, + { + "expr": "MacAddressConfigError", + "isPrintf": true + }, + { + "expr": "VlanConfigError", + "isPrintf": true + }, + { + "expr": "VlanInitError", + "isPrintf": true + }, + { + "expr": "PhyDriverConfigError", + "isPrintf": true + }, + { + "expr": "PhyDriverInitError", + "isPrintf": true + }, + { + "expr": "Ip4ConfigError", + "isPrintf": true + }, + { + "expr": "Ip6ConfigError", + "isPrintf": true + }, + { + "expr": "SemaphoreCreateFailed", + "isPrintf": true + }, + { + "expr": "GetOptionInvalidParameter", + "isPrintf": true + }, + { + "expr": "SetOptionInvalidParameter", + "isPrintf": true + }, + { + "expr": "SetMacAddress", + "isPrintf": true + }, + { + "expr": "SetVlanIdentifier", + "isPrintf": true + }, + { + "expr": "SetIp4Address", + "isPrintf": true + }, + { + "expr": "SetIp4SubnetMask", + "isPrintf": true + }, + { + "expr": "SetIp4DefaultGateway", + "isPrintf": true + }, + { + "expr": "SetIp4PrimaryDNS", + "isPrintf": true + }, + { + "expr": "SetIp4SecondaryDNS", + "isPrintf": true + }, + { + "expr": "SetIp4Mtu", + "isPrintf": true + }, + { + "expr": "SetIp6Address", + "isPrintf": true + }, + { + "expr": "SetIp6DefaultGateway", + "isPrintf": true + }, + { + "expr": "SetIp6PrimaryDNS", + "isPrintf": true + }, + { + "expr": "SetIp6SecondaryDNS", + "isPrintf": true + }, + { + "expr": "SetIp6PrefixLength", + "isPrintf": true + }, + { + "expr": "SetIp6Mtu", + "isPrintf": true + }, + { + "expr": "SendFrame", + "isPrintf": true + }, + { + "expr": "LinkDownError", + "isPrintf": true + }, + { + "expr": "SendDataTooLarge", + "isPrintf": true + }, + { + "expr": "SendIp4Disabled", + "isPrintf": true + }, + { + "expr": "Ip4LocalAddressUndefined", + "isPrintf": true + }, + { + "expr": "Ip4MacAddressUnresolved", + "isPrintf": true + }, + { + "expr": "EnqueueFrame", + "isPrintf": true + }, + { + "expr": "SendIp6Disabled", + "isPrintf": true + }, + { + "expr": "Ip6LocalAddressUndefined", + "isPrintf": true + }, + { + "expr": "Ip6MacAddressUnresolved", + "isPrintf": true + }, + { + "expr": "InvalidIpVersion", + "isPrintf": true + }, + { + "expr": "ShowFrameHeader", + "isPrintf": true + }, + { + "expr": "LinkDownStatus", + "isPrintf": true + }, + { + "expr": "LinkUpStatus", + "isPrintf": true + }, + { + "expr": "ReceiveFrame", + "isPrintf": true + }, + { + "expr": "VlanInvalid", + "isPrintf": true + }, + { + "expr": "Ip4Disabled", + "isPrintf": true + }, + { + "expr": "Ip6Disabled", + "isPrintf": true + }, + { + "expr": "ProtocolUnknown", + "isPrintf": true + }, + { + "expr": "SendRawFrame", + "isPrintf": true + }, + { + "expr": "SendRawInvalidParameter", + "isPrintf": true + }, + { + "expr": "OutputLowLevel", + "isPrintf": true + }, + { + "expr": "UnInitInterface", + "isPrintf": true + }, + { + "expr": "DriverInitFailed", + "isPrintf": true + }, + { + "expr": "SetBypassModeFailed", + "isPrintf": true + }, + { + "expr": "GetMacAddressFailed", + "isPrintf": true + }, + { + "expr": "DriverMacAddress", + "isPrintf": true + }, + { + "expr": "SetMacAddressFailed", + "isPrintf": true + }, + { + "expr": "Scan", + "isPrintf": true + }, + { + "expr": "ScanWrongMode", + "isPrintf": true + }, + { + "expr": "ScanInvalidParameter", + "isPrintf": true + }, + { + "expr": "ScanComplete", + "isPrintf": true + }, + { + "expr": "Activate", + "isPrintf": true + }, + { + "expr": "ActivateInvalidParameter", + "isPrintf": true + }, + { + "expr": "Deactivate", + "isPrintf": true + }, + { + "expr": "DeactivateInvalidParam", + "isPrintf": true + }, + { + "expr": "GetNetInfo", + "isPrintf": true + }, + { + "expr": "GetNetInfoWrongMode", + "isPrintf": true + }, + { + "expr": "GetNetInfoInvalidParam", + "isPrintf": true + }, + { + "expr": "NotConnected", + "isPrintf": true + }, + { + "expr": "LinkStateChange", + "isPrintf": true + }, + { + "expr": "TxQueueOverflow", + "isPrintf": true + }, + { + "expr": "OutputNoMemory", + "isPrintf": true + }, + { + "expr": "FrameTooShort", + "isPrintf": true + }, + { + "expr": "ChecksumFailed", + "isPrintf": true + }, + { + "expr": "CtrlByteInvalid", + "isPrintf": true + }, + { + "expr": "NetworkLayerDown", + "isPrintf": true + }, + { + "expr": "NoMemoryError", + "isPrintf": true + }, + { + "expr": "QueueAddTransmit", + "isPrintf": true + }, + { + "expr": "RejectProtocol", + "isPrintf": true + }, + { + "expr": "RejectCode", + "isPrintf": true + }, + { + "expr": "Connect", + "isPrintf": true + }, + { + "expr": "ConnectInvalidParameter", + "isPrintf": true + }, + { + "expr": "ConnectWrongState", + "isPrintf": true + }, + { + "expr": "Listen", + "isPrintf": true + }, + { + "expr": "ListenInvalidParameter", + "isPrintf": true + }, + { + "expr": "ListenWrongState", + "isPrintf": true + }, + { + "expr": "Close", + "isPrintf": true + }, + { + "expr": "ShowUsername", + "isPrintf": true + }, + { + "expr": "ShowPassword", + "isPrintf": true + }, + { + "expr": "ModemOffline", + "isPrintf": true + }, + { + "expr": "DataLinkDown", + "isPrintf": true + }, + { + "expr": "LcpInit", + "isPrintf": true + }, + { + "expr": "LcpSendConfigRequest", + "isPrintf": true + }, + { + "expr": "LcpNoRetriesLeft", + "isPrintf": true + }, + { + "expr": "LcpOptionCharMap", + "isPrintf": true + }, + { + "expr": "LcpOptionPfc", + "isPrintf": true + }, + { + "expr": "LcpOptionAcfc", + "isPrintf": true + }, + { + "expr": "LcpOptionMagicNumber", + "isPrintf": true + }, + { + "expr": "LcpOptionAuthPap", + "isPrintf": true + }, + { + "expr": "LcpOptionAuthChapMd5", + "isPrintf": true + }, + { + "expr": "LcpSendEchoRequest", + "isPrintf": true + }, + { + "expr": "LcpSendTerminateRequest", + "isPrintf": true + }, + { + "expr": "LcpSendFrame", + "isPrintf": true + }, + { + "expr": "LcpReceiveFrame", + "isPrintf": true + }, + { + "expr": "LcpDataLinkUp", + "isPrintf": true + }, + { + "expr": "LcpOptionMru", + "isPrintf": true + }, + { + "expr": "LcpOptionAuth", + "isPrintf": true + }, + { + "expr": "LcpOptionAuthChapNotMd5", + "isPrintf": true + }, + { + "expr": "LcpOptionUnknown", + "isPrintf": true + }, + { + "expr": "LcpSendReject", + "isPrintf": true + }, + { + "expr": "LcpSendNak", + "isPrintf": true + }, + { + "expr": "LcpSendAck", + "isPrintf": true + }, + { + "expr": "LcpWrongAckReceived", + "isPrintf": true + }, + { + "expr": "LcpWrongNakReceived", + "isPrintf": true + }, + { + "expr": "LcpConfigAuthFailed", + "isPrintf": true + }, + { + "expr": "LcpWrongRejectReceived", + "isPrintf": true + }, + { + "expr": "LcpEchoMagicNumber", + "isPrintf": true + }, + { + "expr": "LcpWrongPeerMagicNumber", + "isPrintf": true + }, + { + "expr": "LcpSendEchoReply", + "isPrintf": true + }, + { + "expr": "LcpWrongEchoReplyReceived", + "isPrintf": true + }, + { + "expr": "LcpSendTerminateAck", + "isPrintf": true + }, + { + "expr": "LcpUnInit", + "isPrintf": true + }, + { + "expr": "PapInit", + "isPrintf": true + }, + { + "expr": "PapSendAuthRequest", + "isPrintf": true + }, + { + "expr": "PapShowPassword", + "isPrintf": true + }, + { + "expr": "PapSendFrame", + "isPrintf": true + }, + { + "expr": "PapReceiveFrame", + "isPrintf": true + }, + { + "expr": "PapLoginSuccess", + "isPrintf": true + }, + { + "expr": "PapLoginFailed", + "isPrintf": true + }, + { + "expr": "PapWrongAckReceived", + "isPrintf": true + }, + { + "expr": "PapWrongNakReceived", + "isPrintf": true + }, + { + "expr": "PapUnInit", + "isPrintf": true + }, + { + "expr": "ChapInit", + "isPrintf": true + }, + { + "expr": "ChapSendChallenge", + "isPrintf": true + }, + { + "expr": "ChapSendFrame", + "isPrintf": true + }, + { + "expr": "ChapReceiveFrame", + "isPrintf": true + }, + { + "expr": "ChapWrongResponseReceived", + "isPrintf": true + }, + { + "expr": "ChapWrongSuccessReceived", + "isPrintf": true + }, + { + "expr": "ChapWrongFailureReceived", + "isPrintf": true + }, + { + "expr": "ChapLoginSuccess", + "isPrintf": true + }, + { + "expr": "ChapLoginFailed", + "isPrintf": true + }, + { + "expr": "ChapUnInit", + "isPrintf": true + }, + { + "expr": "IpcpInit", + "isPrintf": true + }, + { + "expr": "IpcpIp4ConfigError", + "isPrintf": true + }, + { + "expr": "IpcpSendConfigRequest", + "isPrintf": true + }, + { + "expr": "IpcpOptionIpAddress", + "isPrintf": true + }, + { + "expr": "IpcpOptionPrimaryDns", + "isPrintf": true + }, + { + "expr": "IpcpOptionSecondaryDns", + "isPrintf": true + }, + { + "expr": "IpcpSendFrame", + "isPrintf": true + }, + { + "expr": "IpcpReceiveFrame", + "isPrintf": true + }, + { + "expr": "IpcpOptionUnknown", + "isPrintf": true + }, + { + "expr": "IpcpSendReject", + "isPrintf": true + }, + { + "expr": "IpcpSendNak", + "isPrintf": true + }, + { + "expr": "IpcpSendAck", + "isPrintf": true + }, + { + "expr": "IpcpWrongAckReceived", + "isPrintf": true + }, + { + "expr": "IpcpNetworkLayerUp", + "isPrintf": true + }, + { + "expr": "IpcpWrongNakReceived", + "isPrintf": true + }, + { + "expr": "IpcpWrongRejectReceived", + "isPrintf": true + }, + { + "expr": "IpcpWrongSubnet", + "isPrintf": true + }, + { + "expr": "IpcpPrimaryDnsRejected", + "isPrintf": true + }, + { + "expr": "IpcpSecondaryDnsRejected", + "isPrintf": true + }, + { + "expr": "IpcpIpAddressRejected", + "isPrintf": true + }, + { + "expr": "IpcpNotRequestedOption", + "isPrintf": true + }, + { + "expr": "IpcpUnInit", + "isPrintf": true + }, + { + "expr": "InitCore", + "isPrintf": true + }, + { + "expr": "SourceIpAddressInvalid", + "isPrintf": true + }, + { + "expr": "WrongMulticastProtocol", + "isPrintf": true + }, + { + "expr": "WrongBroadcastProtocol", + "isPrintf": true + }, + { + "expr": "WrongDestinationAddress", + "isPrintf": true + }, + { + "expr": "FragmentDfFlagSet", + "isPrintf": true + }, + { + "expr": "FragmentationDisabled", + "isPrintf": true + }, + { + "expr": "DestinationAddressNull", + "isPrintf": true + }, + { + "expr": "SetDefaultInterface", + "isPrintf": true + }, + { + "expr": "UnInitCore", + "isPrintf": true + }, + { + "expr": "EchoRequestWrongCode", + "isPrintf": true + }, + { + "expr": "EchoRequestReceived", + "isPrintf": true + }, + { + "expr": "EchoReplyDisabled", + "isPrintf": true + }, + { + "expr": "SendEchoReply", + "isPrintf": true + }, + { + "expr": "EchoReplyReceived", + "isPrintf": true + }, + { + "expr": "EchoReplyWrongState", + "isPrintf": true + }, + { + "expr": "EchoReplyWrongCode", + "isPrintf": true + }, + { + "expr": "EchoReplyWrongIpAddress", + "isPrintf": true + }, + { + "expr": "EchoReplyWrongId", + "isPrintf": true + }, + { + "expr": "EchoReplyWrongPayload", + "isPrintf": true + }, + { + "expr": "MessageTypeUnknown", + "isPrintf": true + }, + { + "expr": "SendEchoRequest", + "isPrintf": true + }, + { + "expr": "PingInit", + "isPrintf": true + }, + { + "expr": "PingEcho", + "isPrintf": true + }, + { + "expr": "PingTargetNotValid", + "isPrintf": true + }, + { + "expr": "PingDnsError", + "isPrintf": true + }, + { + "expr": "PingInvalidParameter", + "isPrintf": true + }, + { + "expr": "PingClientBusy", + "isPrintf": true + }, + { + "expr": "PingSendRequest", + "isPrintf": true + }, + { + "expr": "PingRetransmitRequest", + "isPrintf": true + }, + { + "expr": "PingTimeout", + "isPrintf": true + }, + { + "expr": "PingUnInit", + "isPrintf": true + }, + { + "expr": "InitManager", + "isPrintf": true + }, + { + "expr": "Join", + "isPrintf": true + }, + { + "expr": "AlreadyInGroup", + "isPrintf": true + }, + { + "expr": "NoFreeEntries", + "isPrintf": true + }, + { + "expr": "SendReport", + "isPrintf": true + }, + { + "expr": "Leave", + "isPrintf": true + }, + { + "expr": "NotInGroup", + "isPrintf": true + }, + { + "expr": "SendLeave", + "isPrintf": true + }, + { + "expr": "GroupSpecificQuery", + "isPrintf": true + }, + { + "expr": "DestAddressWrong", + "isPrintf": true + }, + { + "expr": "DelayedReportScheduled", + "isPrintf": true + }, + { + "expr": "GeneralQuery", + "isPrintf": true + }, + { + "expr": "StartModeIGMPv1", + "isPrintf": true + }, + { + "expr": "MaxTimeForReport", + "isPrintf": true + }, + { + "expr": "GroupReportsScheduled", + "isPrintf": true + }, + { + "expr": "NoReportScheduled", + "isPrintf": true + }, + { + "expr": "ReportReceived", + "isPrintf": true + }, + { + "expr": "OwnReportCanceled", + "isPrintf": true + }, + { + "expr": "StartModeIGMPv2", + "isPrintf": true + }, + { + "expr": "SendDelayedReport", + "isPrintf": true + }, + { + "expr": "UnInitManager", + "isPrintf": true + }, + { + "expr": "InitService", + "isPrintf": true + }, + { + "expr": "GetSocketFailed", + "isPrintf": true + }, + { + "expr": "WrongRemotePort", + "isPrintf": true + }, + { + "expr": "NetBiosDisabled", + "isPrintf": true + }, + { + "expr": "NameQueryRequest", + "isPrintf": true + }, + { + "expr": "QueryFromAddress", + "isPrintf": true + }, + { + "expr": "NameQueryResponse", + "isPrintf": true + }, + { + "expr": "WrongTransactionId", + "isPrintf": true + }, + { + "expr": "ResolveInvalidParameter", + "isPrintf": true + }, + { + "expr": "ResolveNetBiosDisabled", + "isPrintf": true + }, + { + "expr": "ResolveClientBusy", + "isPrintf": true + }, + { + "expr": "Resolve", + "isPrintf": true + }, + { + "expr": "ResolvedFromCache", + "isPrintf": true + }, + { + "expr": "ResolveTimeoutExpired", + "isPrintf": true + }, + { + "expr": "ResolvedAddress", + "isPrintf": true + }, + { + "expr": "ClearCacheInvalidParam", + "isPrintf": true + }, + { + "expr": "ClrCacheNetBiosDisabled", + "isPrintf": true + }, + { + "expr": "ClearCacheClientBusy", + "isPrintf": true + }, + { + "expr": "ClearCache", + "isPrintf": true + }, + { + "expr": "ResolveRetransmit", + "isPrintf": true + }, + { + "expr": "UnInitService", + "isPrintf": true + }, + { + "expr": "InitClient", + "isPrintf": true + }, + { + "expr": "StartClient", + "isPrintf": true + }, + { + "expr": "StopClient", + "isPrintf": true + }, + { + "expr": "ClientState", + "isPrintf": true + }, + { + "expr": "NextState", + "isPrintf": true + }, + { + "expr": "StateRetransmit", + "isPrintf": true + }, + { + "expr": "ChangeStateOnTimeout", + "isPrintf": true + }, + { + "expr": "AutoIpAddressProbe", + "isPrintf": true + }, + { + "expr": "AutoIpSuccess", + "isPrintf": true + }, + { + "expr": "T2Expired", + "isPrintf": true + }, + { + "expr": "LeaseExpired", + "isPrintf": true + }, + { + "expr": "SendDhcpMessage", + "isPrintf": true + }, + { + "expr": "WrongServerPort", + "isPrintf": true + }, + { + "expr": "MisformedReply", + "isPrintf": true + }, + { + "expr": "WrongClientHwAddress", + "isPrintf": true + }, + { + "expr": "WrongMagicCookie", + "isPrintf": true + }, + { + "expr": "InvalidMessageType", + "isPrintf": true + }, + { + "expr": "ViewMessage", + "isPrintf": true + }, + { + "expr": "FileOverloadOptions", + "isPrintf": true + }, + { + "expr": "SnameOverloadOptions", + "isPrintf": true + }, + { + "expr": "MissingServerId", + "isPrintf": true + }, + { + "expr": "ForwardedMessage", + "isPrintf": true + }, + { + "expr": "OfferedAddressInvalid", + "isPrintf": true + }, + { + "expr": "ViewRelayAgentAddress", + "isPrintf": true + }, + { + "expr": "ViewOfferedAddress", + "isPrintf": true + }, + { + "expr": "ServerAddressNotSelected", + "isPrintf": true + }, + { + "expr": "AssignedAddrNotRequested", + "isPrintf": true + }, + { + "expr": "ViewAssignedAddress", + "isPrintf": true + }, + { + "expr": "ViewServerId", + "isPrintf": true + }, + { + "expr": "ViewNetMask", + "isPrintf": true + }, + { + "expr": "ViewGatewayAddress", + "isPrintf": true + }, + { + "expr": "ViewDnsServers", + "isPrintf": true + }, + { + "expr": "ViewLeaseTime", + "isPrintf": true + }, + { + "expr": "ViewTimeT1", + "isPrintf": true + }, + { + "expr": "ViewTimeT2", + "isPrintf": true + }, + { + "expr": "ViewBootfileName", + "isPrintf": true + }, + { + "expr": "ViewNtpServerList", + "isPrintf": true + }, + { + "expr": "ViewNtpServerList1", + "isPrintf": true + }, + { + "expr": "ViewNtpServerList2", + "isPrintf": true + }, + { + "expr": "ViewNtpServerList3", + "isPrintf": true + }, + { + "expr": "SetOptionInvalidParam", + "isPrintf": true + }, + { + "expr": "SetOptionInvalidState", + "isPrintf": true + }, + { + "expr": "SetOptionClientIdDefault", + "isPrintf": true + }, + { + "expr": "SetOptionClientId", + "isPrintf": true + }, + { + "expr": "UnInitClient", + "isPrintf": true + }, + { + "expr": "InitCache", + "isPrintf": true + }, + { + "expr": "FrameCorrupted", + "isPrintf": true + }, + { + "expr": "SenderAddressInvalid", + "isPrintf": true + }, + { + "expr": "CacheEntryUpdate", + "isPrintf": true + }, + { + "expr": "ProbeResponseReceived", + "isPrintf": true + }, + { + "expr": "WrongIpAddress", + "isPrintf": true + }, + { + "expr": "WrongMacAddress", + "isPrintf": true + }, + { + "expr": "OpcodeUnknown", + "isPrintf": true + }, + { + "expr": "SendReply", + "isPrintf": true + }, + { + "expr": "CacheEntryRefreshed", + "isPrintf": true + }, + { + "expr": "WrongResponse", + "isPrintf": true + }, + { + "expr": "SendRequest", + "isPrintf": true + }, + { + "expr": "CacheAllocFailed", + "isPrintf": true + }, + { + "expr": "CacheIpRefreshed", + "isPrintf": true + }, + { + "expr": "CacheAdd", + "isPrintf": true + }, + { + "expr": "GatewayUnknown", + "isPrintf": true + }, + { + "expr": "CacheEntryAdded", + "isPrintf": true + }, + { + "expr": "CacheEarly", + "isPrintf": true + }, + { + "expr": "CacheFind", + "isPrintf": true + }, + { + "expr": "UsingGateway", + "isPrintf": true + }, + { + "expr": "UnresolvedMacAddress", + "isPrintf": true + }, + { + "expr": "EntryFound", + "isPrintf": true + }, + { + "expr": "CacheIp", + "isPrintf": true + }, + { + "expr": "CacheIpInvalidParameter", + "isPrintf": true + }, + { + "expr": "CacheMac", + "isPrintf": true + }, + { + "expr": "CacheMacInvalidParameter", + "isPrintf": true + }, + { + "expr": "UnresolvedIpAddress", + "isPrintf": true + }, + { + "expr": "GetIp", + "isPrintf": true + }, + { + "expr": "GetIpInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetIpEntryNotFound", + "isPrintf": true + }, + { + "expr": "GetMac", + "isPrintf": true + }, + { + "expr": "GetMacInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetMacEntryNotFound", + "isPrintf": true + }, + { + "expr": "Probe", + "isPrintf": true + }, + { + "expr": "ProbeInvalidParameter", + "isPrintf": true + }, + { + "expr": "ProbeBusy", + "isPrintf": true + }, + { + "expr": "ProbeRetransmit", + "isPrintf": true + }, + { + "expr": "ProbeTimeout", + "isPrintf": true + }, + { + "expr": "AddCache", + "isPrintf": true + }, + { + "expr": "AddCacheInvalidParam", + "isPrintf": true + }, + { + "expr": "AddCacheInvalidIpAddress", + "isPrintf": true + }, + { + "expr": "AddCacheInvalidMacAddress", + "isPrintf": true + }, + { + "expr": "CacheEntryTimeout", + "isPrintf": true + }, + { + "expr": "EntryReleased", + "isPrintf": true + }, + { + "expr": "ResolveEntry", + "isPrintf": true + }, + { + "expr": "RefreshEntry", + "isPrintf": true + }, + { + "expr": "UnInitCache", + "isPrintf": true + }, + { + "expr": "InterfaceNotSupported", + "isPrintf": true + }, + { + "expr": "SourceAddressNull", + "isPrintf": true + }, + { + "expr": "MessageDiscarded", + "isPrintf": true + }, + { + "expr": "WrongHopLimit", + "isPrintf": true + }, + { + "expr": "WrongCode", + "isPrintf": true + }, + { + "expr": "NextStateDelay", + "isPrintf": true + }, + { + "expr": "ChangeStateLinkDown", + "isPrintf": true + }, + { + "expr": "NoAddressAvailable", + "isPrintf": true + }, + { + "expr": "StateTimeout", + "isPrintf": true + }, + { + "expr": "T1Expired", + "isPrintf": true + }, + { + "expr": "WrongClientId", + "isPrintf": true + }, + { + "expr": "ServerNotChosen", + "isPrintf": true + }, + { + "expr": "StatusCode", + "isPrintf": true + }, + { + "expr": "WrongServerId", + "isPrintf": true + }, + { + "expr": "WrongIaid", + "isPrintf": true + }, + { + "expr": "ViewIanaOffer", + "isPrintf": true + }, + { + "expr": "OfferTimerCheckFailed", + "isPrintf": true + }, + { + "expr": "FrameCheckFailed", + "isPrintf": true + }, + { + "expr": "AddressNotLinkLocal", + "isPrintf": true + }, + { + "expr": "RouterAdvertisement", + "isPrintf": true + }, + { + "expr": "OptionMtu", + "isPrintf": true + }, + { + "expr": "OptionSourceMacAddress", + "isPrintf": true + }, + { + "expr": "OptionPrefixInfo", + "isPrintf": true + }, + { + "expr": "PrefixInfoNotValid", + "isPrintf": true + }, + { + "expr": "WrongTargetAddress", + "isPrintf": true + }, + { + "expr": "NeighborSolicitation", + "isPrintf": true + }, + { + "expr": "ProbeRequest", + "isPrintf": true + }, + { + "expr": "NeighborAdvertisement", + "isPrintf": true + }, + { + "expr": "ProbeResponse", + "isPrintf": true + }, + { + "expr": "ViewFlags", + "isPrintf": true + }, + { + "expr": "ViewTargetAddress", + "isPrintf": true + }, + { + "expr": "OptionTargetMacAddress", + "isPrintf": true + }, + { + "expr": "MakeLinkLocalAddress", + "isPrintf": true + }, + { + "expr": "MakeTempAddress", + "isPrintf": true + }, + { + "expr": "RouterListFull", + "isPrintf": true + }, + { + "expr": "CacheEntryDeleted", + "isPrintf": true + }, + { + "expr": "InitNode", + "isPrintf": true + }, + { + "expr": "AddressSpecificQuery", + "isPrintf": true + }, + { + "expr": "UnInitNode", + "isPrintf": true + }, + { + "expr": "InitSockets", + "isPrintf": true + }, + { + "expr": "GetSocket", + "isPrintf": true + }, + { + "expr": "GetSocketInvalidParameter", + "isPrintf": true + }, + { + "expr": "NoSocketAvailable", + "isPrintf": true + }, + { + "expr": "ReleaseSocket", + "isPrintf": true + }, + { + "expr": "ReleaseSocketNotValid", + "isPrintf": true + }, + { + "expr": "ReleaseSocketWrongState", + "isPrintf": true + }, + { + "expr": "OpenSocket", + "isPrintf": true + }, + { + "expr": "OpenSocketNotValid", + "isPrintf": true + }, + { + "expr": "OpenSocketWrongState", + "isPrintf": true + }, + { + "expr": "AssignLocalPort", + "isPrintf": true + }, + { + "expr": "OpenLocalPortInUse", + "isPrintf": true + }, + { + "expr": "CloseSocket", + "isPrintf": true + }, + { + "expr": "CloseSocketNotValid", + "isPrintf": true + }, + { + "expr": "SetOptionSocket", + "isPrintf": true + }, + { + "expr": "SetOptionSocketNotValid", + "isPrintf": true + }, + { + "expr": "SetOptionSocketWrongState", + "isPrintf": true + }, + { + "expr": "SetOptionTos", + "isPrintf": true + }, + { + "expr": "SetOptionTtl", + "isPrintf": true + }, + { + "expr": "SetOptionTclass", + "isPrintf": true + }, + { + "expr": "SetOptionHopLimit", + "isPrintf": true + }, + { + "expr": "SetOptionChecksum", + "isPrintf": true + }, + { + "expr": "SetOptionInterface", + "isPrintf": true + }, + { + "expr": "SetOptionWrongOption", + "isPrintf": true + }, + { + "expr": "SetOptionWrongValue", + "isPrintf": true + }, + { + "expr": "GetBufferFailed", + "isPrintf": true + }, + { + "expr": "SendBufferInvalid", + "isPrintf": true + }, + { + "expr": "SendSocketNotValid", + "isPrintf": true + }, + { + "expr": "SendAddressUnspecified", + "isPrintf": true + }, + { + "expr": "SendPortUndefined", + "isPrintf": true + }, + { + "expr": "SendSocketNotOpen", + "isPrintf": true + }, + { + "expr": "SendZeroLengthFrame", + "isPrintf": true + }, + { + "expr": "SendNoRouteFound", + "isPrintf": true + }, + { + "expr": "SendSizeTruncated", + "isPrintf": true + }, + { + "expr": "MapFrameToSocket", + "isPrintf": true + }, + { + "expr": "FrameNotMapped", + "isPrintf": true + }, + { + "expr": "LinkLayerAddressed", + "isPrintf": true + }, + { + "expr": "UnInitSockets", + "isPrintf": true + }, + { + "expr": "ListenSocket", + "isPrintf": true + }, + { + "expr": "ListenSocketNotValid", + "isPrintf": true + }, + { + "expr": "ListenPortUndefined", + "isPrintf": true + }, + { + "expr": "ListenSocketWrongState", + "isPrintf": true + }, + { + "expr": "ConnectSocket", + "isPrintf": true + }, + { + "expr": "ConnectSocketNotValid", + "isPrintf": true + }, + { + "expr": "ConnectAddressUnspecified", + "isPrintf": true + }, + { + "expr": "ConnectPortUndefined", + "isPrintf": true + }, + { + "expr": "ShowNetAddress", + "isPrintf": true + }, + { + "expr": "ShowNetAddressIp4", + "isPrintf": true + }, + { + "expr": "ShowNetAddressIp6", + "isPrintf": true + }, + { + "expr": "ConnectLocalPortInvalid", + "isPrintf": true + }, + { + "expr": "ConnectSocketWrongState", + "isPrintf": true + }, + { + "expr": "ConnectNoRouteFound", + "isPrintf": true + }, + { + "expr": "ShowRttVariables", + "isPrintf": true + }, + { + "expr": "SendSocketNotConnected", + "isPrintf": true + }, + { + "expr": "SendSocketClosing", + "isPrintf": true + }, + { + "expr": "SendReenteredCall", + "isPrintf": true + }, + { + "expr": "SendDataUnacked", + "isPrintf": true + }, + { + "expr": "SendMssExceeded", + "isPrintf": true + }, + { + "expr": "CloseDataUnacked", + "isPrintf": true + }, + { + "expr": "CloseSocketWrongState", + "isPrintf": true + }, + { + "expr": "AbortSocket", + "isPrintf": true + }, + { + "expr": "AbortSocketNotValid", + "isPrintf": true + }, + { + "expr": "AbortSocketWrongState", + "isPrintf": true + }, + { + "expr": "SendReadySocketNotValid", + "isPrintf": true + }, + { + "expr": "SendReadyReenteredCall", + "isPrintf": true + }, + { + "expr": "ResetWindowSocketNotValid", + "isPrintf": true + }, + { + "expr": "ResetWindowNotConnected", + "isPrintf": true + }, + { + "expr": "ResetWindowNoFlowControl", + "isPrintf": true + }, + { + "expr": "ResetWindowUpdate", + "isPrintf": true + }, + { + "expr": "SetOptionTimeout", + "isPrintf": true + }, + { + "expr": "SetOptionKeepAlive", + "isPrintf": true + }, + { + "expr": "SetOptionFlowControl", + "isPrintf": true + }, + { + "expr": "SetOptionDelayedAck", + "isPrintf": true + }, + { + "expr": "SendDelayedAck", + "isPrintf": true + }, + { + "expr": "SendKeepAliveProbe", + "isPrintf": true + }, + { + "expr": "KeepAliveTimeoutClosing", + "isPrintf": true + }, + { + "expr": "CallbackEventAck", + "isPrintf": true + }, + { + "expr": "ResendOnTimeout", + "isPrintf": true + }, + { + "expr": "ShowCongestionVariables", + "isPrintf": true + }, + { + "expr": "TimeoutInState", + "isPrintf": true + }, + { + "expr": "TwaitTimeoutClosing", + "isPrintf": true + }, + { + "expr": "ClosingTimeout", + "isPrintf": true + }, + { + "expr": "NoRetriesLeft", + "isPrintf": true + }, + { + "expr": "RstInWindow", + "isPrintf": true + }, + { + "expr": "RstNotValid", + "isPrintf": true + }, + { + "expr": "RepeatedSynAck", + "isPrintf": true + }, + { + "expr": "AckNotSet", + "isPrintf": true + }, + { + "expr": "ShowSendWindow", + "isPrintf": true + }, + { + "expr": "KeepAliveSegment", + "isPrintf": true + }, + { + "expr": "RetransmittedSegment", + "isPrintf": true + }, + { + "expr": "OutOfRangeSegment", + "isPrintf": true + }, + { + "expr": "ZeroWindowProbe", + "isPrintf": true + }, + { + "expr": "RemotePeerClosing", + "isPrintf": true + }, + { + "expr": "ShowReceiveWindow", + "isPrintf": true + }, + { + "expr": "InvalidAck", + "isPrintf": true + }, + { + "expr": "SynNotSet", + "isPrintf": true + }, + { + "expr": "UserConnectionReject", + "isPrintf": true + }, + { + "expr": "WrongSynAck", + "isPrintf": true + }, + { + "expr": "WrongAckNumber", + "isPrintf": true + }, + { + "expr": "WrongSeqNumber", + "isPrintf": true + }, + { + "expr": "RepeatedSyn", + "isPrintf": true + }, + { + "expr": "FrameUnrecognised", + "isPrintf": true + }, + { + "expr": "SimultOpenNextState", + "isPrintf": true + }, + { + "expr": "WrongFinAck", + "isPrintf": true + }, + { + "expr": "FinAckNextState", + "isPrintf": true + }, + { + "expr": "SimultCloseNextState", + "isPrintf": true + }, + { + "expr": "AckNextState", + "isPrintf": true + }, + { + "expr": "FinNextState", + "isPrintf": true + }, + { + "expr": "PshAckInHalfClosed", + "isPrintf": true + }, + { + "expr": "RepeatedFin", + "isPrintf": true + }, + { + "expr": "LastAckNextState", + "isPrintf": true + }, + { + "expr": "RstReceived", + "isPrintf": true + }, + { + "expr": "InvalidState", + "isPrintf": true + }, + { + "expr": "SendData", + "isPrintf": true + }, + { + "expr": "SendControl", + "isPrintf": true + }, + { + "expr": "SendReset", + "isPrintf": true + }, + { + "expr": "ParseHeaderOptions", + "isPrintf": true + }, + { + "expr": "OptionMss", + "isPrintf": true + }, + { + "expr": "DuplicateAck", + "isPrintf": true + }, + { + "expr": "FastRetransmit", + "isPrintf": true + }, + { + "expr": "DataAcked", + "isPrintf": true + }, + { + "expr": "ResendData", + "isPrintf": true + }, + { + "expr": "MapSocketWrongFlagsSet", + "isPrintf": true + }, + { + "expr": "MapSocketSynNotSet", + "isPrintf": true + }, + { + "expr": "MapSocketNoListenSocket", + "isPrintf": true + }, + { + "expr": "SocketCreate", + "isPrintf": true + }, + { + "expr": "SocketInvalidParameter", + "isPrintf": true + }, + { + "expr": "BindSocket", + "isPrintf": true + }, + { + "expr": "BindSocketNotValid", + "isPrintf": true + }, + { + "expr": "BindInvalidParameter", + "isPrintf": true + }, + { + "expr": "BindSocketNotCreated", + "isPrintf": true + }, + { + "expr": "BindSocketAlreadyBound", + "isPrintf": true + }, + { + "expr": "BindSocketConnected", + "isPrintf": true + }, + { + "expr": "BindPortInUse", + "isPrintf": true + }, + { + "expr": "ViewNetAddress", + "isPrintf": true + }, + { + "expr": "ViewNetAddressIp4", + "isPrintf": true + }, + { + "expr": "ViewNetAddressIp6", + "isPrintf": true + }, + { + "expr": "ListenSocketNotCreated", + "isPrintf": true + }, + { + "expr": "ListenSocketNotBound", + "isPrintf": true + }, + { + "expr": "ListenSocketNotStreamType", + "isPrintf": true + }, + { + "expr": "ListenSockAlreadyListens", + "isPrintf": true + }, + { + "expr": "ListenBacklogCreateFailed", + "isPrintf": true + }, + { + "expr": "AcceptSocket", + "isPrintf": true + }, + { + "expr": "AcceptSocketNotValid", + "isPrintf": true + }, + { + "expr": "AcceptSocketNotCreated", + "isPrintf": true + }, + { + "expr": "AcceptSocketNotListening", + "isPrintf": true + }, + { + "expr": "AcceptSocketNotStreamType", + "isPrintf": true + }, + { + "expr": "AcceptSocketLocked", + "isPrintf": true + }, + { + "expr": "AcceptChildSockConnected", + "isPrintf": true + }, + { + "expr": "AcceptSocketClosed", + "isPrintf": true + }, + { + "expr": "AcceptSocketKilled", + "isPrintf": true + }, + { + "expr": "ConnectSocketLocked", + "isPrintf": true + }, + { + "expr": "ConnectSocketNotCreated", + "isPrintf": true + }, + { + "expr": "ConnectDatagramSuccess", + "isPrintf": true + }, + { + "expr": "ConnectStreamSuccess", + "isPrintf": true + }, + { + "expr": "ConnectStreamTimeout", + "isPrintf": true + }, + { + "expr": "ConnectStreamRefused", + "isPrintf": true + }, + { + "expr": "ConnectSocketKilled", + "isPrintf": true + }, + { + "expr": "SendSocket", + "isPrintf": true + }, + { + "expr": "SendInvalidParameter", + "isPrintf": true + }, + { + "expr": "SendSocketNotCreated", + "isPrintf": true + }, + { + "expr": "SendSocketLocked", + "isPrintf": true + }, + { + "expr": "SendSocketWrongState", + "isPrintf": true + }, + { + "expr": "SendSocketClosed", + "isPrintf": true + }, + { + "expr": "SendCompleteNonBlocking", + "isPrintf": true + }, + { + "expr": "SendSocketKilled", + "isPrintf": true + }, + { + "expr": "SendStreamError", + "isPrintf": true + }, + { + "expr": "SendtoInvalidParameter", + "isPrintf": true + }, + { + "expr": "SendDestAddressUndefined", + "isPrintf": true + }, + { + "expr": "SendtoMsgTooLarge", + "isPrintf": true + }, + { + "expr": "SendtoNoMemory", + "isPrintf": true + }, + { + "expr": "SendDatagramError", + "isPrintf": true + }, + { + "expr": "SendCompleteBlocking", + "isPrintf": true + }, + { + "expr": "SendTimeoutBlocking", + "isPrintf": true + }, + { + "expr": "SendNoMemory", + "isPrintf": true + }, + { + "expr": "SendmsgSocket", + "isPrintf": true + }, + { + "expr": "SendmsgSocketNotValid", + "isPrintf": true + }, + { + "expr": "SendmsgInvalidParameter", + "isPrintf": true + }, + { + "expr": "SendmsgSocketNotCreated", + "isPrintf": true + }, + { + "expr": "SendmsgSocketLocked", + "isPrintf": true + }, + { + "expr": "SendmsgDestAddrUndefined", + "isPrintf": true + }, + { + "expr": "SendmsgMsgTooLarge", + "isPrintf": true + }, + { + "expr": "SendmsgNoMemory", + "isPrintf": true + }, + { + "expr": "SendmsgDatagramError", + "isPrintf": true + }, + { + "expr": "SendmsgSocketKilled", + "isPrintf": true + }, + { + "expr": "SendmsgComplete", + "isPrintf": true + }, + { + "expr": "RecvSocket", + "isPrintf": true + }, + { + "expr": "RecvSocketNotValid", + "isPrintf": true + }, + { + "expr": "RecvInvalidParameter", + "isPrintf": true + }, + { + "expr": "RecvSocketNotCreated", + "isPrintf": true + }, + { + "expr": "RecvSocketNotConnected", + "isPrintf": true + }, + { + "expr": "RecvSocketWrongState", + "isPrintf": true + }, + { + "expr": "RecvSocketLocked", + "isPrintf": true + }, + { + "expr": "RecvSocketClosed", + "isPrintf": true + }, + { + "expr": "RecvTimeout", + "isPrintf": true + }, + { + "expr": "RecvSocketKilled", + "isPrintf": true + }, + { + "expr": "RecvQueueFree", + "isPrintf": true + }, + { + "expr": "RecvComplete", + "isPrintf": true + }, + { + "expr": "RecvmsgSocket", + "isPrintf": true + }, + { + "expr": "RecvmsgSocketNotValid", + "isPrintf": true + }, + { + "expr": "RecvmsgInvalidParameter", + "isPrintf": true + }, + { + "expr": "RecvmsgSocketNotCreated", + "isPrintf": true + }, + { + "expr": "RecvmsgSocketLocked", + "isPrintf": true + }, + { + "expr": "RecvmsgSocketWrongState", + "isPrintf": true + }, + { + "expr": "RecvmsgTimeout", + "isPrintf": true + }, + { + "expr": "RecvmsgSocketKilled", + "isPrintf": true + }, + { + "expr": "RecvmsgComplete", + "isPrintf": true + }, + { + "expr": "Closesocket", + "isPrintf": true + }, + { + "expr": "CloseSocketNotCreated", + "isPrintf": true + }, + { + "expr": "Select", + "isPrintf": true + }, + { + "expr": "SelectInvalidParameter", + "isPrintf": true + }, + { + "expr": "SelectSuspendFailed", + "isPrintf": true + }, + { + "expr": "SelectComplete", + "isPrintf": true + }, + { + "expr": "SelectCompleteBlocking", + "isPrintf": true + }, + { + "expr": "Getpeername", + "isPrintf": true + }, + { + "expr": "GetpeerSocketNotValid", + "isPrintf": true + }, + { + "expr": "GetpeerSocketNotCreated", + "isPrintf": true + }, + { + "expr": "GetpeerInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetpeerSocketNotConnected", + "isPrintf": true + }, + { + "expr": "Getsockname", + "isPrintf": true + }, + { + "expr": "GetsockSocketNotValid", + "isPrintf": true + }, + { + "expr": "GetsockSocketNotCreated", + "isPrintf": true + }, + { + "expr": "GetsockInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetsockSocketNotBound", + "isPrintf": true + }, + { + "expr": "Setsockopt", + "isPrintf": true + }, + { + "expr": "SetoptSocketNotValid", + "isPrintf": true + }, + { + "expr": "SetoptInvalidParameter", + "isPrintf": true + }, + { + "expr": "SetoptSocketNotCreated", + "isPrintf": true + }, + { + "expr": "SetoptOptionNotSupported", + "isPrintf": true + }, + { + "expr": "SetoptKeepAlive", + "isPrintf": true + }, + { + "expr": "SetoptRecvTimeout", + "isPrintf": true + }, + { + "expr": "SetoptSendTimeout", + "isPrintf": true + }, + { + "expr": "SetoptBindToDevice", + "isPrintf": true + }, + { + "expr": "SetoptIp4Tos", + "isPrintf": true + }, + { + "expr": "SetoptIp4Ttl", + "isPrintf": true + }, + { + "expr": "SetoptIp4RecvDstAddr", + "isPrintf": true + }, + { + "expr": "SetoptIp6Tclass", + "isPrintf": true + }, + { + "expr": "SetoptIp6HopLimit", + "isPrintf": true + }, + { + "expr": "SetoptIp6RecvDstAddr", + "isPrintf": true + }, + { + "expr": "SetoptIp6Only", + "isPrintf": true + }, + { + "expr": "SetoptSocketBound", + "isPrintf": true + }, + { + "expr": "Getsockopt", + "isPrintf": true + }, + { + "expr": "GetoptSocketNotValid", + "isPrintf": true + }, + { + "expr": "GetoptInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetoptSocketNotCreated", + "isPrintf": true + }, + { + "expr": "GetoptOptionNotSupported", + "isPrintf": true + }, + { + "expr": "Ioctlsocket", + "isPrintf": true + }, + { + "expr": "IoctlSocketNotValid", + "isPrintf": true + }, + { + "expr": "IoctlInvalidParameter", + "isPrintf": true + }, + { + "expr": "IoctlSocketNotCreated", + "isPrintf": true + }, + { + "expr": "IoctlSocketNotStreamType", + "isPrintf": true + }, + { + "expr": "IoctlNonBlocking", + "isPrintf": true + }, + { + "expr": "IoctlDelayAck", + "isPrintf": true + }, + { + "expr": "IoctlKeepAlive", + "isPrintf": true + }, + { + "expr": "IoctlFlowControl", + "isPrintf": true + }, + { + "expr": "CbfuncTcpEvent", + "isPrintf": true + }, + { + "expr": "CbfuncTcpQueueAdd", + "isPrintf": true + }, + { + "expr": "CbfuncTcpNoMemory", + "isPrintf": true + }, + { + "expr": "CbfuncUdpQueueAdd", + "isPrintf": true + }, + { + "expr": "CbfuncUdpDumpData", + "isPrintf": true + }, + { + "expr": "GetHostInit", + "isPrintf": true + }, + { + "expr": "GetHostByName", + "isPrintf": true + }, + { + "expr": "GetHostNameResolved", + "isPrintf": true + }, + { + "expr": "GetHostNameNotExisting", + "isPrintf": true + }, + { + "expr": "GetHostResolverTimeout", + "isPrintf": true + }, + { + "expr": "GetHostResolverError", + "isPrintf": true + }, + { + "expr": "GetHostResolverBusy", + "isPrintf": true + }, + { + "expr": "GetHostInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetHostUnInit", + "isPrintf": true + }, + { + "expr": "InitServer", + "isPrintf": true + }, + { + "expr": "ViewRootFolder", + "isPrintf": true + }, + { + "expr": "SetRootPath", + "isPrintf": true + }, + { + "expr": "SetUsername", + "isPrintf": true + }, + { + "expr": "SetPassword", + "isPrintf": true + }, + { + "expr": "StartService", + "isPrintf": true + }, + { + "expr": "StopService", + "isPrintf": true + }, + { + "expr": "FileNotFound", + "isPrintf": true + }, + { + "expr": "FileAccessForbidden", + "isPrintf": true + }, + { + "expr": "FileCached", + "isPrintf": true + }, + { + "expr": "CloseSession", + "isPrintf": true + }, + { + "expr": "SendFile", + "isPrintf": true + }, + { + "expr": "CgiSendFile", + "isPrintf": true + }, + { + "expr": "CgiIncludeFile", + "isPrintf": true + }, + { + "expr": "CgiStopEngine", + "isPrintf": true + }, + { + "expr": "CgiScriptError", + "isPrintf": true + }, + { + "expr": "UnauthorizedAccess", + "isPrintf": true + }, + { + "expr": "MethodNotImplemented", + "isPrintf": true + }, + { + "expr": "UserAccessDenied", + "isPrintf": true + }, + { + "expr": "UserAccessDeniedIp4", + "isPrintf": true + }, + { + "expr": "UserAccessDeniedIp6", + "isPrintf": true + }, + { + "expr": "SetCookie", + "isPrintf": true + }, + { + "expr": "SessionOpen", + "isPrintf": true + }, + { + "expr": "SocketAborted", + "isPrintf": true + }, + { + "expr": "SocketClosed", + "isPrintf": true + }, + { + "expr": "RequestMethod", + "isPrintf": true + }, + { + "expr": "CgiStartEngine", + "isPrintf": true + }, + { + "expr": "RequestedFile", + "isPrintf": true + }, + { + "expr": "FileOpenDrive", + "isPrintf": true + }, + { + "expr": "ViewFileStatus", + "isPrintf": true + }, + { + "expr": "ViewRedirectionUrl", + "isPrintf": true + }, + { + "expr": "ViewUserCredentials", + "isPrintf": true + }, + { + "expr": "ViewAcceptLanguage", + "isPrintf": true + }, + { + "expr": "ViewCookie", + "isPrintf": true + }, + { + "expr": "ViewXmlType", + "isPrintf": true + }, + { + "expr": "PostXmlEncoded", + "isPrintf": true + }, + { + "expr": "PostCompleteMultipacket", + "isPrintf": true + }, + { + "expr": "PostFormUrlEncoded", + "isPrintf": true + }, + { + "expr": "PostDataTruncated", + "isPrintf": true + }, + { + "expr": "FileUploadRequested", + "isPrintf": true + }, + { + "expr": "FileUploadDataReceived", + "isPrintf": true + }, + { + "expr": "FileUploadRemaining", + "isPrintf": true + }, + { + "expr": "FileUploadComplete", + "isPrintf": true + }, + { + "expr": "TlsGetContextFailed", + "isPrintf": true + }, + { + "expr": "UnInitServer", + "isPrintf": true + }, + { + "expr": "ShowRootFolder", + "isPrintf": true + }, + { + "expr": "UnackedDataError", + "isPrintf": true + }, + { + "expr": "ShowCommand", + "isPrintf": true + }, + { + "expr": "VerifyUsername", + "isPrintf": true + }, + { + "expr": "VerifyPassword", + "isPrintf": true + }, + { + "expr": "AuthenticationFailed", + "isPrintf": true + }, + { + "expr": "UserLoginSuccess", + "isPrintf": true + }, + { + "expr": "NotAuthenticated", + "isPrintf": true + }, + { + "expr": "ShowSystemType", + "isPrintf": true + }, + { + "expr": "NoOperation", + "isPrintf": true + }, + { + "expr": "CurrentDirectory", + "isPrintf": true + }, + { + "expr": "ChangeDirectory", + "isPrintf": true + }, + { + "expr": "ChangeDirectoryFailed", + "isPrintf": true + }, + { + "expr": "ChangeDirectoryLevelUp", + "isPrintf": true + }, + { + "expr": "MakeDirectory", + "isPrintf": true + }, + { + "expr": "OperationDenied", + "isPrintf": true + }, + { + "expr": "RemoveDirectory", + "isPrintf": true + }, + { + "expr": "RemoveDirectoryFailed", + "isPrintf": true + }, + { + "expr": "DirectoryRemoved", + "isPrintf": true + }, + { + "expr": "ShowDataMode", + "isPrintf": true + }, + { + "expr": "ActiveModeStart", + "isPrintf": true + }, + { + "expr": "PassiveModeStart", + "isPrintf": true + }, + { + "expr": "GetFileSize", + "isPrintf": true + }, + { + "expr": "GetFileLastModifiedTime", + "isPrintf": true + }, + { + "expr": "ListDirectoryBasic", + "isPrintf": true + }, + { + "expr": "ListDirectoryExtended", + "isPrintf": true + }, + { + "expr": "ReadFile", + "isPrintf": true + }, + { + "expr": "WriteFile", + "isPrintf": true + }, + { + "expr": "AppendFile", + "isPrintf": true + }, + { + "expr": "FileCreateFailed", + "isPrintf": true + }, + { + "expr": "DeleteFile", + "isPrintf": true + }, + { + "expr": "FileDeleteFailed", + "isPrintf": true + }, + { + "expr": "FileDeleted", + "isPrintf": true + }, + { + "expr": "RenameFileFrom", + "isPrintf": true + }, + { + "expr": "RenameFileTo", + "isPrintf": true + }, + { + "expr": "FileRenamed", + "isPrintf": true + }, + { + "expr": "FileRenameFailed", + "isPrintf": true + }, + { + "expr": "UnknownCommand", + "isPrintf": true + }, + { + "expr": "InboundConnRejected", + "isPrintf": true + }, + { + "expr": "DataSocketClosed", + "isPrintf": true + }, + { + "expr": "DataSocketOpen", + "isPrintf": true + }, + { + "expr": "LocalDiskWriteError", + "isPrintf": true + }, + { + "expr": "ShowFileFindMask", + "isPrintf": true + }, + { + "expr": "MakeDirectoryFailed", + "isPrintf": true + }, + { + "expr": "DirectoryCreated", + "isPrintf": true + }, + { + "expr": "ShowFileSize", + "isPrintf": true + }, + { + "expr": "ShowFileLastModifiedTime", + "isPrintf": true + }, + { + "expr": "CloseDataConnection", + "isPrintf": true + }, + { + "expr": "SessionIdle", + "isPrintf": true + }, + { + "expr": "ShowPath", + "isPrintf": true + }, + { + "expr": "ConnectIp4", + "isPrintf": true + }, + { + "expr": "ConnectIp6", + "isPrintf": true + }, + { + "expr": "ConnectClientBusy", + "isPrintf": true + }, + { + "expr": "SocketConnected", + "isPrintf": true + }, + { + "expr": "ShowReplyCode", + "isPrintf": true + }, + { + "expr": "ResponseFragmented", + "isPrintf": true + }, + { + "expr": "ServerReady", + "isPrintf": true + }, + { + "expr": "UserOkNeedPassword", + "isPrintf": true + }, + { + "expr": "UserLoginFailed", + "isPrintf": true + }, + { + "expr": "WorkingDirectoryInvalid", + "isPrintf": true + }, + { + "expr": "ExecuteUserCommand", + "isPrintf": true + }, + { + "expr": "BinaryModeEnabled", + "isPrintf": true + }, + { + "expr": "PasvCommandFailed", + "isPrintf": true + }, + { + "expr": "PortCommandFailed", + "isPrintf": true + }, + { + "expr": "FileNotFoundOnServer", + "isPrintf": true + }, + { + "expr": "OperationNotAllowed", + "isPrintf": true + }, + { + "expr": "AboutToOpenDataConn", + "isPrintf": true + }, + { + "expr": "DataConnAlreadyOpen", + "isPrintf": true + }, + { + "expr": "TransferAborted", + "isPrintf": true + }, + { + "expr": "TransferCompleted", + "isPrintf": true + }, + { + "expr": "NewNameRequired", + "isPrintf": true + }, + { + "expr": "FileOrDirectoryRenamed", + "isPrintf": true + }, + { + "expr": "FileOrPathNotFound", + "isPrintf": true + }, + { + "expr": "CommandErrorResponse", + "isPrintf": true + }, + { + "expr": "DataSocketOpened", + "isPrintf": true + }, + { + "expr": "ClientStopTimeoutExpired", + "isPrintf": true + }, + { + "expr": "LocalPortAssigned", + "isPrintf": true + }, + { + "expr": "OpenLocalFile", + "isPrintf": true + }, + { + "expr": "LocalFileCreateFailed", + "isPrintf": true + }, + { + "expr": "LocalFileNotFound", + "isPrintf": true + }, + { + "expr": "OpenDataConnFailed", + "isPrintf": true + }, + { + "expr": "SendCommand", + "isPrintf": true + }, + { + "expr": "ClientCloseSocket", + "isPrintf": true + }, + { + "expr": "ClientDone", + "isPrintf": true + }, + { + "expr": "CloseLocalFile", + "isPrintf": true + }, + { + "expr": "ProcessData", + "isPrintf": true + }, + { + "expr": "ProcessCommand", + "isPrintf": true + }, + { + "expr": "EchoBackspace", + "isPrintf": true + }, + { + "expr": "CommandHistory", + "isPrintf": true + }, + { + "expr": "EchoCharacters", + "isPrintf": true + }, + { + "expr": "LineBufferUsage", + "isPrintf": true + }, + { + "expr": "SendAuthorizationRequest", + "isPrintf": true + }, + { + "expr": "SendInitialHeader", + "isPrintf": true + }, + { + "expr": "LoginTimeoutExpired", + "isPrintf": true + }, + { + "expr": "NegotiateStart", + "isPrintf": true + }, + { + "expr": "NegotiateFailed", + "isPrintf": true + }, + { + "expr": "NegotiateSuccess", + "isPrintf": true + }, + { + "expr": "TimeoutExpiredAbort", + "isPrintf": true + }, + { + "expr": "SendBlock", + "isPrintf": true + }, + { + "expr": "AccessDeniedNoResources", + "isPrintf": true + }, + { + "expr": "ShowClientAddress", + "isPrintf": true + }, + { + "expr": "ShowClientAddressIp4", + "isPrintf": true + }, + { + "expr": "ShowClientAddressIp6", + "isPrintf": true + }, + { + "expr": "OperationRequest", + "isPrintf": true + }, + { + "expr": "SessionRestart", + "isPrintf": true + }, + { + "expr": "InvalidTransferId", + "isPrintf": true + }, + { + "expr": "ErrorCodeReceived", + "isPrintf": true + }, + { + "expr": "IllegalOpcodeReceived", + "isPrintf": true + }, + { + "expr": "FileRequested", + "isPrintf": true + }, + { + "expr": "TransferBlockSize", + "isPrintf": true + }, + { + "expr": "TransferModeNotBinary", + "isPrintf": true + }, + { + "expr": "ReceiveDataFrame", + "isPrintf": true + }, + { + "expr": "DataFrameTooShort", + "isPrintf": true + }, + { + "expr": "DuplicateBlockReceived", + "isPrintf": true + }, + { + "expr": "TooManyRetries", + "isPrintf": true + }, + { + "expr": "BlockReceived", + "isPrintf": true + }, + { + "expr": "InvalidBlockReceived", + "isPrintf": true + }, + { + "expr": "WriteErrorDiskFull", + "isPrintf": true + }, + { + "expr": "BlockAckReceived", + "isPrintf": true + }, + { + "expr": "BlockRetransmit", + "isPrintf": true + }, + { + "expr": "InvalidBlockAck", + "isPrintf": true + }, + { + "expr": "ShowRequestMode", + "isPrintf": true + }, + { + "expr": "SendOptionAck", + "isPrintf": true + }, + { + "expr": "SendBlockAck", + "isPrintf": true + }, + { + "expr": "SendError", + "isPrintf": true + }, + { + "expr": "AbortSession", + "isPrintf": true + }, + { + "expr": "PutFile", + "isPrintf": true + }, + { + "expr": "PutRemoteName", + "isPrintf": true + }, + { + "expr": "PutInvalidParameter", + "isPrintf": true + }, + { + "expr": "PutWrongState", + "isPrintf": true + }, + { + "expr": "ShowServerAddress", + "isPrintf": true + }, + { + "expr": "ShowServerAddressIp4", + "isPrintf": true + }, + { + "expr": "ShowServerAddressIp6", + "isPrintf": true + }, + { + "expr": "OpenLocalFileFailed", + "isPrintf": true + }, + { + "expr": "GetFile", + "isPrintf": true + }, + { + "expr": "GetLocalName", + "isPrintf": true + }, + { + "expr": "GetInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetWrongState", + "isPrintf": true + }, + { + "expr": "TimeoutBlockRetransmit", + "isPrintf": true + }, + { + "expr": "WrongServerAddress", + "isPrintf": true + }, + { + "expr": "WrongServerAddressIp4", + "isPrintf": true + }, + { + "expr": "WrongServerAddressIp6", + "isPrintf": true + }, + { + "expr": "ServerTidAssigned", + "isPrintf": true + }, + { + "expr": "DuplicateBlockAck", + "isPrintf": true + }, + { + "expr": "IllegalServerOperation", + "isPrintf": true + }, + { + "expr": "OptionBlockSize", + "isPrintf": true + }, + { + "expr": "SendAck", + "isPrintf": true + }, + { + "expr": "OptionAckReceived", + "isPrintf": true + }, + { + "expr": "SendMail", + "isPrintf": true + }, + { + "expr": "SendMailInvalidParameter", + "isPrintf": true + }, + { + "expr": "SendMailTlsNotEnabled", + "isPrintf": true + }, + { + "expr": "SendMailClientBusy", + "isPrintf": true + }, + { + "expr": "SendMailNoRecipients", + "isPrintf": true + }, + { + "expr": "SendMailServerNotValid", + "isPrintf": true + }, + { + "expr": "SendMailDnsError", + "isPrintf": true + }, + { + "expr": "SendMailAttachNotEnabled", + "isPrintf": true + }, + { + "expr": "SendMailAttachFailed", + "isPrintf": true + }, + { + "expr": "SendMailMultipart", + "isPrintf": true + }, + { + "expr": "SendMailAttachment", + "isPrintf": true + }, + { + "expr": "EsmtpModeNotSupported", + "isPrintf": true + }, + { + "expr": "EhloResponseFragmented", + "isPrintf": true + }, + { + "expr": "EsmtpModeActive", + "isPrintf": true + }, + { + "expr": "StartAuthentication", + "isPrintf": true + }, + { + "expr": "AuthenticationDenied", + "isPrintf": true + }, + { + "expr": "AuthMethodNotSupported", + "isPrintf": true + }, + { + "expr": "SmtpModeActive", + "isPrintf": true + }, + { + "expr": "AuthenticationSuccessful", + "isPrintf": true + }, + { + "expr": "ServerAcknowledge", + "isPrintf": true + }, + { + "expr": "SendMessageBody", + "isPrintf": true + }, + { + "expr": "SendMessageEnd", + "isPrintf": true + }, + { + "expr": "TlsSupportIndicated", + "isPrintf": true + }, + { + "expr": "StartTlsAccepted", + "isPrintf": true + }, + { + "expr": "TlsModeStarted", + "isPrintf": true + }, + { + "expr": "TlsModeEstablished", + "isPrintf": true + }, + { + "expr": "ChangeDnsServer", + "isPrintf": true + }, + { + "expr": "ReceiveFrameIp4", + "isPrintf": true + }, + { + "expr": "ReceiveFrameIp6", + "isPrintf": true + }, + { + "expr": "DnsRequestReceived", + "isPrintf": true + }, + { + "expr": "OpcodeNotQuery", + "isPrintf": true + }, + { + "expr": "MessageTruncated", + "isPrintf": true + }, + { + "expr": "NoSuchNameFound", + "isPrintf": true + }, + { + "expr": "RcodeAndRecursion", + "isPrintf": true + }, + { + "expr": "MoreAnswersReceived", + "isPrintf": true + }, + { + "expr": "QnameNotTheSame", + "isPrintf": true + }, + { + "expr": "QtypeNotTheSame", + "isPrintf": true + }, + { + "expr": "QclassNotInet", + "isPrintf": true + }, + { + "expr": "GotHostAddress", + "isPrintf": true + }, + { + "expr": "GotHostAddressIp4", + "isPrintf": true + }, + { + "expr": "GotHostAddressIp6", + "isPrintf": true + }, + { + "expr": "GotAuthorityAddress", + "isPrintf": true + }, + { + "expr": "GotAuthorityAddressIp4", + "isPrintf": true + }, + { + "expr": "GotAuthorityAddressIp6", + "isPrintf": true + }, + { + "expr": "RecordTypeNotSupported", + "isPrintf": true + }, + { + "expr": "ResolvedAddressIp4", + "isPrintf": true + }, + { + "expr": "ResolvedAddressIp6", + "isPrintf": true + }, + { + "expr": "ResolveDnsServerUnknown", + "isPrintf": true + }, + { + "expr": "SendRequestIp4", + "isPrintf": true + }, + { + "expr": "SendRequestIp6", + "isPrintf": true + }, + { + "expr": "InitAgent", + "isPrintf": true + }, + { + "expr": "ViewCommunity", + "isPrintf": true + }, + { + "expr": "SetCommunity", + "isPrintf": true + }, + { + "expr": "SetCommunityInvalidParam", + "isPrintf": true + }, + { + "expr": "SetMibTable", + "isPrintf": true + }, + { + "expr": "SetMibTableInvalidParam", + "isPrintf": true + }, + { + "expr": "Trap", + "isPrintf": true + }, + { + "expr": "TrapInvalidParameter", + "isPrintf": true + }, + { + "expr": "TrapMibTableNotSet", + "isPrintf": true + }, + { + "expr": "TrapMissingSysObjectId", + "isPrintf": true + }, + { + "expr": "TrapGenericTrapInvalid", + "isPrintf": true + }, + { + "expr": "TrapTooManyObjects", + "isPrintf": true + }, + { + "expr": "TrapObjectNotExisting", + "isPrintf": true + }, + { + "expr": "TrapMessageTooBig", + "isPrintf": true + }, + { + "expr": "MibAddObject", + "isPrintf": true + }, + { + "expr": "ViewObjectId", + "isPrintf": true + }, + { + "expr": "ViewObjectVal", + "isPrintf": true + }, + { + "expr": "ViewObjectVal-Integer", + "isPrintf": true + }, + { + "expr": "ViewObjectVal-Counter", + "isPrintf": true + }, + { + "expr": "ViewObjectVal-Gauge", + "isPrintf": true + }, + { + "expr": "ViewObjectVal-Ticks", + "isPrintf": true + }, + { + "expr": "ViewObjectAddr", + "isPrintf": true + }, + { + "expr": "ViewObjectVar", + "isPrintf": true + }, + { + "expr": "ViewObjectVar-OID", + "isPrintf": true + }, + { + "expr": "ViewObjectVar-String", + "isPrintf": true + }, + { + "expr": "ViewObjectVar-BString", + "isPrintf": true + }, + { + "expr": "FrameProtocolError", + "isPrintf": true + }, + { + "expr": "VersionNotSupported", + "isPrintf": true + }, + { + "expr": "WrongCommunityReceived", + "isPrintf": true + }, + { + "expr": "InvalidRequestType", + "isPrintf": true + }, + { + "expr": "ViewRequest", + "isPrintf": true + }, + { + "expr": "TooManyObjectsReceived", + "isPrintf": true + }, + { + "expr": "ObjectNotFound", + "isPrintf": true + }, + { + "expr": "ObjectWrongType", + "isPrintf": true + }, + { + "expr": "ObjectReadOnly", + "isPrintf": true + }, + { + "expr": "ObjectWriteFailed", + "isPrintf": true + }, + { + "expr": "SendErrorStatus", + "isPrintf": true + }, + { + "expr": "ResponseMessageTooBig", + "isPrintf": true + }, + { + "expr": "SendGetResponse", + "isPrintf": true + }, + { + "expr": "UnInitAgent", + "isPrintf": true + }, + { + "expr": "GetTime", + "isPrintf": true + }, + { + "expr": "GetTimeInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetTimeWrongState", + "isPrintf": true + }, + { + "expr": "GetTimeOpen", + "isPrintf": true + }, + { + "expr": "GetTimeClose", + "isPrintf": true + }, + { + "expr": "GetTimeServerUnknown", + "isPrintf": true + }, + { + "expr": "GetTimexInvalidParameter", + "isPrintf": true + }, + { + "expr": "GetTimexClientBusy", + "isPrintf": true + }, + { + "expr": "GetTimexServerNotValid", + "isPrintf": true + }, + { + "expr": "GetTimexDnsError", + "isPrintf": true + }, + { + "expr": "SetMode", + "isPrintf": true + }, + { + "expr": "SetModeInvalidParameter", + "isPrintf": true + }, + { + "expr": "SetModeWrongState", + "isPrintf": true + }, + { + "expr": "SendMessage", + "isPrintf": true + }, + { + "expr": "ServerNotResponding", + "isPrintf": true + }, + { + "expr": "ModeNotServer", + "isPrintf": true + }, + { + "expr": "ModeNotBroadcast", + "isPrintf": true + }, + { + "expr": "AnswerInWrongState", + "isPrintf": true + }, + { + "expr": "ShowTimeStamp", + "isPrintf": true + }, + { + "expr": "TimeStampInvalid", + "isPrintf": true + }, + { + "expr": "105+ 0xC300", + "isPrintf": false + }, + { + "expr": "eth0_en += (eth0.localm ? 2 : 0) + (eth0.localm6 ? 4 : 0);", + "isPrintf": false + }, + { + "expr": "eth1_en += (eth1.localm ? 2 : 0) + (eth1.localm6 ? 4 : 0);", + "isPrintf": false + }, + { + "expr": "wifi0_en += (wifi0.localm ? 2 : 0) + (wifi0.localm6 ? 4 : 0);", + "isPrintf": false + }, + { + "expr": "wifi1_en += (wifi1.localm ? 2 : 0) + (wifi1.localm6 ? 4 : 0);", + "isPrintf": false + }, + { + "expr": "udp_used++;", + "isPrintf": false + }, + { + "expr": "tcp_used++;", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd0_dev\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd1_dev\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd2_dev\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd3_dev\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_custom_class\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc0_out_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc0_in_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc1_out_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc1_in_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc2_out_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc2_in_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc3_out_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_adc3_in_data\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_cdc\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_hid\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbd_msc\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh0_pipe\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh0_hc_ptr\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh1_pipe\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh1_hc_ptr\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh2_pipe\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh2_hc_ptr\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh3_pipe\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh3_hc_ptr\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh_dev\")", + "isPrintf": false + }, + { + "expr": "__Symbol_exists(\"usbh_msc\")", + "isPrintf": false + }, + { + "expr": "%x[debug_val]", + "isPrintf": true + }, + { + "expr": "%d[usb_lib_version_major].%d[usb_lib_version_minor].%d[usb_lib_version_patch]", + "isPrintf": true + }, + { + "expr": "%x[usbd0_device_desc.idVendor]", + "isPrintf": true + }, + { + "expr": "%x[usbd0_device_desc.idProduct]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_cfg.hs]", + "isPrintf": true + }, + { + "expr": "%d[usbd0_device_desc.bMaxPacketSize0]", + "isPrintf": true + }, + { + "expr": "%d[usbd0_config_desc.bNumInterfaces]", + "isPrintf": true + }, + { + "expr": "%d[usbd0_data.device_address]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.configuration]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep0_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep0_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep1_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep1_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep2_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep2_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep3_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep3_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep4_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep4_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep5_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep5_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep6_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep6_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep7_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep7_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep8_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep8_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep9_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep9_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep10_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep10_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep11_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep11_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep12_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep12_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep13_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep13_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep14_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep14_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep15_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd0_data.ep15_in_active ]", + "isPrintf": true + }, + { + "expr": "%d[usbd_cc[0].if_num] interface(s)", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc0_out_data.ch_num], %d[usbd_adc0_out_data.data_freq] Hz, %d[usbd_adc0_out_data.sample_res] bits/sample, EP ISO OUT: %d[usbd_adc0_out_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc0_spkr_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc0_spkr_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc0_spkr_data.vol_cur0]/%d[usbd_adc0_spkr_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc0_spkr_data.vol_cur1]/%d[usbd_adc0_spkr_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc0_in_data.ch_num], %d[usbd_adc0_in_data.data_freq] Hz, %d[usbd_adc0_in_data.sample_res] bits/sample, EP ISO IN: %d[usbd_adc0_in_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc0_mic_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc0_mic_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc0_mic_data.vol_cur0]/%d[usbd_adc0_mic_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc0_mic_data.vol_cur1]/%d[usbd_adc0_mic_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc1_out_data.ch_num], %d[usbd_adc1_out_data.data_freq] Hz, %d[usbd_adc1_out_data.sample_res] bits/sample, EP ISO OUT: %d[usbd_adc1_out_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc1_spkr_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc1_spkr_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc1_spkr_data.vol_cur0]/%d[usbd_adc1_spkr_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc1_spkr_data.vol_cur1]/%d[usbd_adc1_spkr_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc1_in_data.ch_num], %d[usbd_adc1_in_data.data_freq] Hz, %d[usbd_adc1_in_data.sample_res] bits/sample, EP ISO IN: %d[usbd_adc1_in_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc1_mic_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc1_mic_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc1_mic_data.vol_cur0]/%d[usbd_adc1_mic_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc1_mic_data.vol_cur1]/%d[usbd_adc1_mic_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc2_out_data.ch_num], %d[usbd_adc2_out_data.data_freq] Hz, %d[usbd_adc2_out_data.sample_res] bits/sample, EP ISO OUT: %d[usbd_adc2_out_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc2_spkr_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc2_spkr_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc2_spkr_data.vol_cur0]/%d[usbd_adc2_spkr_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc2_spkr_data.vol_cur1]/%d[usbd_adc2_spkr_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc2_in_data.ch_num], %d[usbd_adc2_in_data.data_freq] Hz, %d[usbd_adc2_in_data.sample_res] bits/sample, EP ISO IN: %d[usbd_adc2_in_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc2_mic_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc2_mic_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc2_mic_data.vol_cur0]/%d[usbd_adc2_mic_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc2_mic_data.vol_cur1]/%d[usbd_adc2_mic_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc3_out_data.ch_num], %d[usbd_adc3_out_data.data_freq] Hz, %d[usbd_adc3_out_data.sample_res] bits/sample, EP ISO OUT: %d[usbd_adc3_out_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc3_spkr_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc3_spkr_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc3_spkr_data.vol_cur0]/%d[usbd_adc3_spkr_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc3_spkr_data.vol_cur1]/%d[usbd_adc3_spkr_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc3_in_data.ch_num], %d[usbd_adc3_in_data.data_freq] Hz, %d[usbd_adc3_in_data.sample_res] bits/sample, EP ISO IN: %d[usbd_adc0_in_data.ep_iso]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc3_mic_data.active]", + "isPrintf": true + }, + { + "expr": "%E[usbd_adc3_mic_data.mute]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc3_mic_data.vol_cur0]/%d[usbd_adc3_mic_data.vol_max0]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc3_mic_data.vol_cur1]/%d[usbd_adc3_mic_data.vol_max1]", + "isPrintf": true + }, + { + "expr": "Subclass: %E[usbd_cdc[0].acm], EP INT IN: %d[usbd_cdc[0].ep_int_in], EP BULK IN: %d[usbd_cdc[0].ep_bulk_in], EP BULK OUT: %d[usbd_cdc[0].ep_bulk_out]", + "isPrintf": true + }, + { + "expr": "In reports %d[usbd_hid[0].in_report_num], Out reports %d[usbd_hid[0].out_report_num], EP INT IN: %d[usbd_hid[0].ep_int_in], EP INT OUT: %E[usbd_hid[0].ep_int_out]", + "isPrintf": true + }, + { + "expr": "EP BULK IN: %d[usbd_msc[0].ep_bulk_in], EP BULK OUT: %E[usbd_msc[0].ep_bulk_out]", + "isPrintf": true + }, + { + "expr": "%d[usbd_msc_data[0].lun0_media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun0_media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun0_media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun0_media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "%d[usbd_msc_data[0].lun1_media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun1_media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun1_media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun1_media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "%d[usbd_msc_data[0].lun2_media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun2_media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun2_media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun2_media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "%d[usbd_msc_data[0].lun3_media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun3_media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun3_media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbd_msc_data[0].lun3_media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "%x[usbd1_device_desc.idVendor]", + "isPrintf": true + }, + { + "expr": "%x[usbd1_device_desc.idProduct]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_cfg.hs]", + "isPrintf": true + }, + { + "expr": "%d[usbd1_device_desc.bMaxPacketSize0]", + "isPrintf": true + }, + { + "expr": "%d[usbd1_config_desc.bNumInterfaces]", + "isPrintf": true + }, + { + "expr": "%d[usbd1_data.device_address]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.configuration]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep0_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep0_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep1_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep1_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep2_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep2_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep3_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep3_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep4_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep4_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep5_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep5_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep6_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep6_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep7_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep7_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep8_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep8_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep9_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep9_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep10_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep10_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep11_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep11_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep12_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep12_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep13_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep13_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep14_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep14_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep15_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd1_data.ep15_in_active ]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc0_spkr_data.vol_cur[0]]/%d[usbd_adc0_spkr_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc0_mic_data.vol_cur[0]]/%d[usbd_adc0_mic_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc1_spkr_data.vol_cur[0]]/%d[usbd_adc1_spkr_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc1_mic_data.vol_cur[0]]/%d[usbd_adc1_mic_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc2_spkr_data.vol_cur[0]]/%d[usbd_adc2_spkr_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc2_mic_data.vol_cur[0]]/%d[usbd_adc2_mic_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc3_spkr_data.vol_cur[0]]/%d[usbd_adc3_spkr_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%d[usbd_adc3_mic_data.vol_cur[0]]/%d[usbd_adc3_mic_data.vol_max[0]]", + "isPrintf": true + }, + { + "expr": "%x[usbd2_device_desc.idVendor]", + "isPrintf": true + }, + { + "expr": "%x[usbd2_device_desc.idProduct]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_cfg.hs]", + "isPrintf": true + }, + { + "expr": "%d[usbd2_device_desc.bMaxPacketSize0]", + "isPrintf": true + }, + { + "expr": "%d[usbd2_config_desc.bNumInterfaces]", + "isPrintf": true + }, + { + "expr": "%d[usbd2_data.device_address]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.configuration]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep0_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep0_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep1_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep1_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep2_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep2_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep3_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep3_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep4_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep4_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep5_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep5_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep6_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep6_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep7_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep7_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep8_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep8_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep9_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep9_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep10_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep10_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep11_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep11_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep12_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep12_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep13_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep13_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep14_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep14_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep15_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd2_data.ep15_in_active ]", + "isPrintf": true + }, + { + "expr": "%x[usbd3_device_desc.idVendor]", + "isPrintf": true + }, + { + "expr": "%x[usbd3_device_desc.idProduct]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_cfg.hs]", + "isPrintf": true + }, + { + "expr": "%d[usbd3_device_desc.bMaxPacketSize0]", + "isPrintf": true + }, + { + "expr": "%d[usbd3_config_desc.bNumInterfaces]", + "isPrintf": true + }, + { + "expr": "%d[usbd3_data.device_address]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.configuration]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep0_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep0_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep1_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep1_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep2_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep2_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep3_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep3_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep4_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep4_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep5_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep5_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep6_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep6_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep7_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep7_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep8_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep8_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep9_out_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep9_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep10_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep10_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep11_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep11_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep12_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep12_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep13_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep13_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep14_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep14_in_active ]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep15_out_active]", + "isPrintf": true + }, + { + "expr": "%E[usbd3_data.ep15_in_active ]", + "isPrintf": true + }, + { + "expr": "In use %d[usbh0_pipe_used_num] of %d[usbh0_pipe_num]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh0_disp_i].vid]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh0_disp_i].pid]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh0_disp_i].state]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh0_disp_i].dev_addr]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh0_disp_i].dev_speed]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh0_disp_i].max_packet_size]", + "isPrintf": true + }, + { + "expr": "Custom Class", + "isPrintf": false + }, + { + "expr": "Communication Device Class", + "isPrintf": false + }, + { + "expr": "Human Interface Device", + "isPrintf": false + }, + { + "expr": "Mass Storage", + "isPrintf": false + }, + { + "expr": "%d[usbh_msc[usbh0_disp_i].media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh0_disp_i].media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh0_disp_i].media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh0_disp_i].media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "In use %d[usbh1_pipe_used_num] of %d[usbh1_pipe_num]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh1_disp_i].vid]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh1_disp_i].pid]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh1_disp_i].state]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh1_disp_i].dev_addr]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh1_disp_i].dev_speed]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh1_disp_i].max_packet_size]", + "isPrintf": true + }, + { + "expr": "%d[usbh_msc[usbh1_disp_i].media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh1_disp_i].media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh1_disp_i].media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh1_disp_i].media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "In use %d[usbh2_pipe_used_num] of %d[usbh2_pipe_num]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh2_disp_i].vid]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh2_disp_i].pid]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh2_disp_i].state]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh2_disp_i].dev_addr]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh2_disp_i].dev_speed]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh2_disp_i].max_packet_size]", + "isPrintf": true + }, + { + "expr": "%d[usbh_msc[usbh2_disp_i].media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh2_disp_i].media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh2_disp_i].media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh2_disp_i].media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "In use %d[usbh3_pipe_used_num] of %d[usbh3_pipe_num]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh3_disp_i].vid]", + "isPrintf": true + }, + { + "expr": "%x[usbh_dev[usbh3_disp_i].pid]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh3_disp_i].state]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh3_disp_i].dev_addr]", + "isPrintf": true + }, + { + "expr": "%E[usbh_dev[usbh3_disp_i].dev_speed]", + "isPrintf": true + }, + { + "expr": "%d[usbh_dev[usbh3_disp_i].max_packet_size]", + "isPrintf": true + }, + { + "expr": "%d[usbh_msc[usbh3_disp_i].media_size] B", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh3_disp_i].media_size_xB] kB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh3_disp_i].media_size_xB] MB", + "isPrintf": true + }, + { + "expr": "%T[usbh_msc[usbh3_disp_i].media_size_xB] GB", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], configured=%E[val2.B0, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], state.VBUS = %E[val2 & 1, usb_bool_enum:on_off], state.speed = %E[(val2 >> 1) & 3, usbDriver_speed_enum:speed], state.active = %E[(val2 >> 3) & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], result=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], len=%d[val3]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], stall=%d[val1.B2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], stall=%d[val1.B2], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], setup_packet=[%x[val2.B0],%x[val2.B1],%x[val2.B2],%x[val2.B3],%x[val3.B0],%x[val3.B1],%x[val3.B2],%x[val3.B3]], wLength=%d[val3.DB1]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], level=%E[val2.B0, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], configuration=%d[val2]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], result=%E[val2.B0, usbdRequestStatus:value]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], setup_packet=[%x[val2.B0],%x[val2.B1],%x[val2.B2],%x[val2.B3],%x[val3.B0],%x[val3.B1],%x[val3.B2],%x[val3.B3]], len=%d[val4]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], setup_packet=[%x[val2.B0],%x[val2.B1],%x[val2.B2],%x[val2.B3],%x[val3.B0],%x[val3.B1],%x[val3.B2],%x[val3.B3]]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], len=%d[val2], result=%E[val1.B1, usbdRequestStatus:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], state=%E[val1.B1, usbDriver_PowCtrl_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], state=%E[val1.B1, usbDriver_PowCtrl_enum:value], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], dev_addr=%x[val1.B1]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], dev_addr=%x[val1.B1], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], setup_packet=[%x[val2.B0],%x[val2.B1],%x[val2.B2],%x[val2.B3],%x[val3.B0],%x[val3.B1],%x[val3.B2],%x[val3.B3]]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], ep_type=%E[val1.B2, usbEpType_enum:value], ep_max_packet_size=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], ep_type=%E[val1.B2, usbEpType_enum:value], ep_max_packet_size=%d[val2], error=%E[val1.B3, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], stall=%d[val1.B2], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], data=%x[val2], num=%d[val3]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], num=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], frame_number=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], event=%E[val2.B0, usbdDriver_SigDevEvt_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], event=%E[val2.B0, usbdDriver_SigEpEvt_enum:value]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], ep_addr=%x[val2.B0]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], ep_num=%d[val1.B1], event=%E[val2, usbdDriver_SigEpEvt_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], ch=%d[val1.B1], on=%E[val2 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], ch=%d[val1.B1], vol=%d[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], ch=%d[val1.B1], min=%d[val2 & 0xFFFF], max=%d[(val2 >> 16) & 0xFFFF], res=%d[val3 & 0xFFFF], cur=%d[(val3 >> 16) & 0xFFFF]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], ch=%d[val1.B1], min=%d[val2 & 0xFFFF], max=%d[(val2 >> 16) & 0xFFFF], res=%d[val3 & 0xFFFF], cur=%d[(val3 >> 16) & 0xFFFF], error=%E[val4.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], result=%d[val2]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], buf=%d[val2], len=%d[val1.DB1], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], max_len=%d[val3], buf=%d[val2], len=%d[val1.DB1], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], rate=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], dtr=%d[val1.B1], rts=%d[val1.B2], result=%E[val2 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], len=%d[val2], result=%d[val3]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], len=%d[val2], error=%E[val3.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], state=%x[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], state=%x[val2], error=%E[val1.B3, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], filter_number=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], packet_filter_bitmap=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], feature_selector=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], ntb_format=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], ntb_input_size=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], max_datagram_size=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], crc_mode=%d[val2], result=%E[val1.B1 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], num_datagrams=%d[val1.B1]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], num_datagrams=%d[val1.B1], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], len=%d[val2], error=%E[val1.B3, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], result=%E[val2 & 1, usb_bool_enum:true_false]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], max_len=%d[val2], result=%d[val3]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], max_len=%d[val2], error=%E[val1.B3, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], max_len=%d[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], status=%d[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], status=%d[val2], error=%E[val1.B3, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], us_bitrate=%d[val2], ds_bitrate=%d[val3]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], us_bitrate=%d[val2], ds_bitrate=%d[val3], error=%E[val4.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], error=%E[val1.B3, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], rid=%d[val1.B1], idle=%d[val1.B2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], protocol=%d[val2.B0]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], rtype=%E[val1.B1, usbdHID_rtype_enum:value], req=%E[val1.B2, usbdHID_req_enum:value], rid=%d[val1.B3], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], rtype=%E[val1.B1, usbdHID_rtype_enum:value], req=%E[val1.B2, usbdHID_req_enum:value], rid=%d[val1.B3]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], protocol=%d[val2.B0]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], rid=%d[val2], buf=%x[val3], len=%d[val4]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], lun=%d[val1.B1]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], lun=%d[val1.B1], status=%E[val2.B0, usbdMSC_status_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], lun=%d[val1.B1], lba=%d[val2], cnt=%d[val3]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], op_code=%d[val2]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], size=%d[val2]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], lun=%d[val1.B1], block_count=%d[val2], block_size=%d[val3]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], lun=%d[val1.B1]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], lun=%d[val1.B1], lba=%d[val2], cnt=%d[val3], buf=%x[val4]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], lun=%d[val1.B1], lba=%d[val2]", + "isPrintf": true + }, + { + "expr": "n=%d[val1.B0], lun=%d[val1.B1], wp=%d[(val2 >> 1) & 1], mp=%d[val2 & 1]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ctrl=%d[val2.B0]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], port=%d[val2.B0]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], status=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], speed=%E[val2.B0, usbDriver_speed_enum:speed]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], address=%d[val2.B0]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], vid=%x[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], pid=%x[val2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], device=%d[val1.B2], notify=%E[val2.B0, usbhNotify_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], ep_type=%d[val1.B2], ep_max_packet_size=%d[val2.DB0], ep_interval=%d[val2.B2], pipe_handle=%x[val3]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], ep_addr=%x[val1.B1], ep_type=%d[val1.B2], ep_max_packet_size=%d[val2.DB0], ep_interval=%d[val2.B2]", + "isPrintf": true + }, + { + "expr": "pipe_handle=%x[val1]", + "isPrintf": true + }, + { + "expr": "pipe_handle=%x[val1], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "pipe_handle=%x[val1], len=%d[val2]", + "isPrintf": true + }, + { + "expr": "pipe_handle=%x[val1], len=%d[val2], error=%E[val3.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "pipe_handle=%x[val1], num=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], setup_packet=[%x[val2.B0],%x[val2.B1],%x[val2.B2],%x[val2.B3],%x[val3.B0],%x[val3.B1],%x[val3.B2],%x[val3.B3]], len=%d[val4]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], setup_packet=[%x[val2.B0],%x[val2.B1],%x[val2.B2],%x[val2.B3],%x[val3.B0],%x[val3.B1],%x[val3.B2],%x[val3.B3]], error=%E[val4.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], recipient=%d[val1.B1], index=%d[val1.B2], status=%d[val2.DB0]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], recipient=%d[val1.B1], index=%d[val1.B2], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], recipient=%d[val1.B1], index=%d[val1.B2], feature=%d[val1.B3]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], recipient=%d[val1.B1], index=%d[val1.B2], feature=%d[val1.B3], error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], address=%x[val1.B1]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], address=%x[val1.B1], error=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], recipient=%d[val1.B1], desc_type=%d[val1.B2], desc_index=%d[val1.B3], lang_id=%d[val2.DB1], desc_len=%d[val2.DB0]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], recipient=%d[val1.B1], desc_type=%d[val1.B2], desc_index=%d[val1.B3], lang_id=%d[val2.DB1], desc_len=%d[val2.DB0], error=%E[val3.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], config=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], error=%E[val2, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], config=%d[val1.B1]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], config=%d[val1.B1], error=%E[val2, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B1], alt=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B1], error=%E[val2, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B1], alt=%d[val1.B2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B1], alt=%d[val1.B2], error=%E[val2, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B1], frame_num=%d[val2.DB0]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], size=%d[val3], error=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], ptr=%x[val2], size=%d[val3]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], ptr=%x[val2], error=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], ptr=%x[val2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], event=%d[val2], port_state=%d[val1.B2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], event=%d[val2]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B3], lang_id=%d[val2.B2], desc_len=%d[val2.DB0], error=%E[val2.B3, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], index=%d[val1.B3], lang_id=%d[val2.B2], desc_len=%d[val2.DB0]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], state=%E[val1.B1, usbDriver_PowCtrl_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], state=%E[val1.B1, usbDriver_PowCtrl_enum:value], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], vbus=%d[val1.B2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], vbus=%d[val1.B2], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], error=%E[val2.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], state.connected = %E[val2 & 1, usb_bool_enum:on_off], state.overcurrent = %E[(val2 >> 1) & 1, usb_bool_enum:on_off], state.speed = %E[(val2 >> 2) & 3, usbDriver_speed_enum:speed]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], dev_addr=%x[val1.B1], dev_speed = %E[val1.B2, usbDriver_speed_enum:speed], hub_addr=%x[val1.B3], hub_port=%d[val2.B0], ep_addr=%x[val2.B1], ep_type=%d[val2.B2], ep_max_packet_size=%d[val3.DB0], ep_interval=%d[val3.B2], pipe_hndl=%x[val4]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], dev_addr=%x[val1.B1], dev_speed = %E[val1.B2, usbDriver_speed_enum:speed], hub_addr=%x[val1.B3], hub_port=%d[val2.B0], ep_addr=%x[val2.B1], ep_type=%d[val2.B2], ep_max_packet_size=%d[val3.DB0], ep_interval=%d[val3.B2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val3], dev_addr=%x[val1.B1], dev_speed = %E[val1.B2, usbDriver_speed_enum:speed], hub_addr=%x[val1.B3], hub_port=%d[val2.B2], ep_max_packet_size=%d[val2.DB0]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val3], dev_addr=%x[val1.B1], dev_speed = %E[val1.B2, usbDriver_speed_enum:speed], hub_addr=%x[val1.B3], hub_port=%d[val2.B2], ep_max_packet_size=%d[val2.DB0], error=%E[val4.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val2], error=%E[val1.B1, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val2], packet=%x[val1.B1], num=%d[val3]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val2], packet=%x[val1.B1], num=%d[val3], error=%E[val4.B0, usbDriver_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val2], num=%d[val3]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], frame_number=%d[val2]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], port=%d[val1.B1], event=%E[val2, usbhDriver_SigPortEvt_enum:value]", + "isPrintf": true + }, + { + "expr": "ctrl=%d[val1.B0], pipe_hndl=%x[val2], event=%E[val1.B1, usbhDriver_SigPipeEvt_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], device=%d[val2.B0]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], status=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "device=%d[val1.B0], class=%x[val1.B1], sub_class=%x[val1.B2], vid=%x[val2.DB1], pid=%x[val2.DB0], instance=%d[val1.B3]", + "isPrintf": true + }, + { + "expr": "error=%E[val1.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], num=%d[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], num=%d[val2], status=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], rate=%d[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], rate=%d[val2], status=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], dtr=%d[val2.B0], rts=%d[val2.B1]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], dtr=%d[val2.B0], rts=%d[val2.B1], status=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], status=%x[val2.DB0]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], duration=%d[val2.DB0]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], duration=%d[val2.DB0], status=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], status=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "error=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], len=%d[val2], status=%E[val1.B1, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], ch=%x[val2]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], status=%E[val4.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], state.button=%d[val2], state.x=%d[val3.DB1], state.y=%d[val3.DB0]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], lba=%d[val2], cnt=%d[val3]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], lba=%d[val2], cnt=%d[val3], status=%E[val4.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], block_addr=%d[val2], block_num=%d[val3]", + "isPrintf": true + }, + { + "expr": "instance=%d[val1.B0], block_addr=%d[val2], block_num=%d[val3], status=%E[val2.B0, usbStatus_enum:value]", + "isPrintf": true + }, + { + "expr": "usbd0_en", + "isPrintf": false + }, + { + "expr": "usbd1_en", + "isPrintf": false + }, + { + "expr": "usbd2_en", + "isPrintf": false + }, + { + "expr": "usbd3_en", + "isPrintf": false + }, + { + "expr": "usbd_cc_en", + "isPrintf": false + }, + { + "expr": "(usbd_cc_en == 1)", + "isPrintf": false + }, + { + "expr": "(usbd_cc_num>0)", + "isPrintf": false + }, + { + "expr": "usbd_adc_en", + "isPrintf": false + }, + { + "expr": "(usbd_adc_en == 1)", + "isPrintf": false + }, + { + "expr": "usbd_adc0_spkr_en", + "isPrintf": false + }, + { + "expr": "usbd_adc0_mic_en", + "isPrintf": false + }, + { + "expr": "usbd_adc1_spkr_en", + "isPrintf": false + }, + { + "expr": "usbd_adc1_mic_en", + "isPrintf": false + }, + { + "expr": "usbd_adc2_spkr_en", + "isPrintf": false + }, + { + "expr": "usbd_adc2_mic_en", + "isPrintf": false + }, + { + "expr": "usbd_adc3_spkr_en", + "isPrintf": false + }, + { + "expr": "usbd_adc3_mic_en", + "isPrintf": false + }, + { + "expr": "(usbd_adc_num>0)", + "isPrintf": false + }, + { + "expr": "usbd_cdc_en", + "isPrintf": false + }, + { + "expr": "(usbd_cdc_en == 1)", + "isPrintf": false + }, + { + "expr": "(usbd_cdc_num>0)", + "isPrintf": false + }, + { + "expr": "usbd_hid_en", + "isPrintf": false + }, + { + "expr": "(usbd_hid_en == 1)", + "isPrintf": false + }, + { + "expr": "(usbd_hid_num>0)", + "isPrintf": false + }, + { + "expr": "usbd_msc_en", + "isPrintf": false + }, + { + "expr": "(usbd_msc_en == 1)", + "isPrintf": false + }, + { + "expr": "(usbd_msc_num>0)", + "isPrintf": false + }, + { + "expr": "(usbd_msc[usb_i].max_lun>0)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size > (1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size > (1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size > (1024*1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size > (1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size > (1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size > (1024*1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size > (1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size > (1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size > (1024*1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size > (1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size > (1024*1024)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size > (1024*1024*1024)", + "isPrintf": false + }, + { + "expr": "usbh0_hc_used", + "isPrintf": false + }, + { + "expr": "usbh0_en", + "isPrintf": false + }, + { + "expr": "usbh1_hc_used", + "isPrintf": false + }, + { + "expr": "usbh1_en", + "isPrintf": false + }, + { + "expr": "usbh2_hc_used", + "isPrintf": false + }, + { + "expr": "usbh2_en", + "isPrintf": false + }, + { + "expr": "usbh3_hc_used", + "isPrintf": false + }, + { + "expr": "usbh3_en", + "isPrintf": false + }, + { + "expr": "usbh_dev_en", + "isPrintf": false + }, + { + "expr": "(usbh_dev_en == 1)", + "isPrintf": false + }, + { + "expr": "usbh_msc_en", + "isPrintf": false + }, + { + "expr": "(usbh_msc_en == 1)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size > (1024)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size > (1024*1024)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size > (1024*1024*1024)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep0_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep0_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep1_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep1_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep2_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep2_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep3_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep3_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep4_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep4_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep5_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep5_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep6_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep6_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep7_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep7_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep8_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep8_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep9_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep9_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep10_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep10_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep11_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep11_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep12_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep12_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep13_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep13_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep14_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep14_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_ep15_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_ep15_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd0_cc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_adc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd_adc0_spkr_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc0_out_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd_adc0_mic_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc0_in_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd0_adc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd_adc1_spkr_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc1_out_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd_adc1_mic_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc1_in_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd0_adc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd_adc2_spkr_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc2_out_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd_adc2_mic_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc2_in_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd0_adc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd_adc3_spkr_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc3_out_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd_adc3_mic_en==1)", + "isPrintf": false + }, + { + "expr": "usbd_adc3_in_data.ch_num==1", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc4_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc5_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc6_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_cdc7_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid0_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid1_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid2_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid3_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid4_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid5_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid6_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_hid7_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_msc0_en)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[0].lun0_media_size_x == 0", + "isPrintf": false + }, + { + "expr": "(usbd_msc[0].max_lun>0)", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[0].lun1_media_size_x == 0", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[0].lun2_media_size_x == 0", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[0].lun3_media_size_x == 0", + "isPrintf": false + }, + { + "expr": "(usbd0_msc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_msc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd0_msc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep0_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep0_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep1_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep1_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep2_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep2_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep3_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep3_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep4_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep4_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep5_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep5_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep6_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep6_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep7_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep7_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep8_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep8_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep9_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep9_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep10_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep10_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep11_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep11_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep12_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep12_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep13_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep13_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep14_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep14_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_ep15_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_ep15_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd1_cc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_adc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_adc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_adc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_adc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc4_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc5_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc6_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_cdc7_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid0_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid1_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid2_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid3_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid4_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid5_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid6_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_hid7_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_msc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_msc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_msc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd1_msc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep0_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep0_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep1_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep1_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep2_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep2_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep3_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep3_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep4_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep4_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep5_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep5_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep6_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep6_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep7_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep7_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep8_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep8_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep9_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep9_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep10_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep10_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep11_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep11_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep12_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep12_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep13_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep13_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep14_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep14_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_ep15_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_ep15_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd2_cc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_adc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_adc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_adc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_adc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc4_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc5_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc6_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_cdc7_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid0_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid1_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid2_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid3_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid4_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid5_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid6_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_hid7_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_msc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_msc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_msc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd2_msc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep0_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep0_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep1_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep1_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep2_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep2_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep3_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep3_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep4_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep4_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep5_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep5_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep6_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep6_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep7_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep7_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep8_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep8_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep9_out_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep9_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep10_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep10_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep11_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep11_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep12_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep12_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep13_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep13_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep14_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep14_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_ep15_out_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_ep15_in_en )", + "isPrintf": false + }, + { + "expr": "(usbd3_cc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_adc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_adc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_adc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_adc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc3_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc4_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc5_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc6_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_cdc7_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid0_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid1_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid2_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid3_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid4_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid5_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid6_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_hid7_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_msc0_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_msc1_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_msc2_en)", + "isPrintf": false + }, + { + "expr": "(usbd3_msc3_en)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh0_disp_i].ctrl == 0) && (usbh_dev[usbh0_disp_i].dev_addr != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh0_disp_i].ctrl == 0) && (usbh_dev[usbh0_disp_i].dev_addr != 0) && (usbh_dev[usbh0_disp_i].class_custom != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh0_disp_i].ctrl == 0) && (usbh_dev[usbh0_disp_i].dev_addr != 0) && (usbh_dev[usbh0_disp_i].class_custom == 0) && (usbh_dev[usbh0_disp_i].class_driver==2)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usbh0_disp_i].media_size_x == 0", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh1_disp_i].ctrl == 1) && (usbh_dev[usbh1_disp_i].dev_addr != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh1_disp_i].ctrl == 1) && (usbh_dev[usbh1_disp_i].dev_addr != 0) && (usbh_dev[usbh1_disp_i].class_custom != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh1_disp_i].ctrl == 1) && (usbh_dev[usbh1_disp_i].dev_addr != 0) && (usbh_dev[usbh1_disp_i].class_custom == 0) && (usbh_dev[usbh1_disp_i].class_driver==2)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usbh1_disp_i].media_size_x == 0", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh2_disp_i].ctrl == 2) && (usbh_dev[usbh2_disp_i].dev_addr != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh2_disp_i].ctrl == 2) && (usbh_dev[usbh2_disp_i].dev_addr != 0) && (usbh_dev[usbh2_disp_i].class_custom != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh2_disp_i].ctrl == 2) && (usbh_dev[usbh2_disp_i].dev_addr != 0) && (usbh_dev[usbh2_disp_i].class_custom == 0) && (usbh_dev[usbh2_disp_i].class_driver==2)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usbh2_disp_i].media_size_x == 0", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh3_disp_i].ctrl == 3) && (usbh_dev[usbh3_disp_i].dev_addr != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh3_disp_i].ctrl == 3) && (usbh_dev[usbh3_disp_i].dev_addr != 0) && (usbh_dev[usbh3_disp_i].class_custom != 0)", + "isPrintf": false + }, + { + "expr": "(usbh_dev[usbh3_disp_i].ctrl == 3) && (usbh_dev[usbh3_disp_i].dev_addr != 0) && (usbh_dev[usbh3_disp_i].class_custom == 0) && (usbh_dev[usbh3_disp_i].class_driver==2)", + "isPrintf": false + }, + { + "expr": "usbh_msc[usbh3_disp_i].media_size_x == 0", + "isPrintf": false + }, + { + "expr": "usb_lib_version", + "isPrintf": false + }, + { + "expr": "usbd0_dev", + "isPrintf": false + }, + { + "expr": "usbd0_device_descriptor", + "isPrintf": false + }, + { + "expr": "usbd0_config_descriptor_fs", + "isPrintf": false + }, + { + "expr": "usbd0_data", + "isPrintf": false + }, + { + "expr": "usbd1_dev", + "isPrintf": false + }, + { + "expr": "usbd1_device_descriptor", + "isPrintf": false + }, + { + "expr": "usbd1_config_descriptor_fs", + "isPrintf": false + }, + { + "expr": "usbd1_data", + "isPrintf": false + }, + { + "expr": "usbd2_dev", + "isPrintf": false + }, + { + "expr": "usbd2_device_descriptor", + "isPrintf": false + }, + { + "expr": "usbd2_config_descriptor_fs", + "isPrintf": false + }, + { + "expr": "usbd2_data", + "isPrintf": false + }, + { + "expr": "usbd3_dev", + "isPrintf": false + }, + { + "expr": "usbd3_device_descriptor", + "isPrintf": false + }, + { + "expr": "usbd3_config_descriptor_fs", + "isPrintf": false + }, + { + "expr": "usbd3_data", + "isPrintf": false + }, + { + "expr": "usbd_custom_class_num", + "isPrintf": false + }, + { + "expr": "usbd_custom_class", + "isPrintf": false + }, + { + "expr": "usbd_adc_num", + "isPrintf": false + }, + { + "expr": "usbd_adc", + "isPrintf": false + }, + { + "expr": "usbd_adc0_spkr_data", + "isPrintf": false + }, + { + "expr": "usbd_adc0_mic_data", + "isPrintf": false + }, + { + "expr": "usbd_adc1_spkr_data", + "isPrintf": false + }, + { + "expr": "usbd_adc1_mic_data", + "isPrintf": false + }, + { + "expr": "usbd_adc2_spkr_data", + "isPrintf": false + }, + { + "expr": "usbd_adc2_mic_data", + "isPrintf": false + }, + { + "expr": "usbd_adc3_spkr_data", + "isPrintf": false + }, + { + "expr": "usbd_adc3_mic_data", + "isPrintf": false + }, + { + "expr": "usbd_adc0_out_data", + "isPrintf": false + }, + { + "expr": "usbd_adc0_in_data", + "isPrintf": false + }, + { + "expr": "usbd_adc1_out_data", + "isPrintf": false + }, + { + "expr": "usbd_adc1_in_data", + "isPrintf": false + }, + { + "expr": "usbd_adc2_out_data", + "isPrintf": false + }, + { + "expr": "usbd_adc2_in_data", + "isPrintf": false + }, + { + "expr": "usbd_adc3_out_data", + "isPrintf": false + }, + { + "expr": "usbd_adc3_in_data", + "isPrintf": false + }, + { + "expr": "usbd_cdc_num", + "isPrintf": false + }, + { + "expr": "usbd_cdc", + "isPrintf": false + }, + { + "expr": "usbd_hid_num", + "isPrintf": false + }, + { + "expr": "usbd_hid", + "isPrintf": false + }, + { + "expr": "usbd_msc_num", + "isPrintf": false + }, + { + "expr": "usbd_msc", + "isPrintf": false + }, + { + "expr": "usbd_msc_data", + "isPrintf": false + }, + { + "expr": "usbh0_hc_ptr", + "isPrintf": false + }, + { + "expr": "usbh0_pipe_num", + "isPrintf": false + }, + { + "expr": "usbh0_pipe", + "isPrintf": false + }, + { + "expr": "usbh1_hc_ptr", + "isPrintf": false + }, + { + "expr": "usbh1_pipe_num", + "isPrintf": false + }, + { + "expr": "usbh1_pipe", + "isPrintf": false + }, + { + "expr": "usbh2_hc_ptr", + "isPrintf": false + }, + { + "expr": "usbh2_pipe_num", + "isPrintf": false + }, + { + "expr": "usbh2_pipe", + "isPrintf": false + }, + { + "expr": "usbh3_hc_ptr", + "isPrintf": false + }, + { + "expr": "usbh3_pipe_num", + "isPrintf": false + }, + { + "expr": "usbh3_pipe", + "isPrintf": false + }, + { + "expr": "usbh_dev_num", + "isPrintf": false + }, + { + "expr": "usbh_dev", + "isPrintf": false + }, + { + "expr": "usbh_msc_num", + "isPrintf": false + }, + { + "expr": "usbh_msc", + "isPrintf": false + }, + { + "expr": "usbd_cc_num", + "isPrintf": false + }, + { + "expr": "usbd_msc_data._count", + "isPrintf": false + }, + { + "expr": "usbh0_pipe._count", + "isPrintf": false + }, + { + "expr": "usbh1_pipe._count", + "isPrintf": false + }, + { + "expr": "usbh2_pipe._count", + "isPrintf": false + }, + { + "expr": "usbh3_pipe._count", + "isPrintf": false + }, + { + "expr": "usbh_msc._count", + "isPrintf": false + }, + { + "expr": "usbh_dev._count", + "isPrintf": false + }, + { + "expr": "Debug Value", + "isPrintf": true + }, + { + "expr": "Library Version", + "isPrintf": true + }, + { + "expr": "Device 0", + "isPrintf": true + }, + { + "expr": "Vendor ID", + "isPrintf": true + }, + { + "expr": "Product ID", + "isPrintf": true + }, + { + "expr": "Speed", + "isPrintf": true + }, + { + "expr": "Endpoint 0 Maximum Packet Size", + "isPrintf": true + }, + { + "expr": "Number of Interfaces", + "isPrintf": true + }, + { + "expr": "Assigned Address", + "isPrintf": true + }, + { + "expr": "Configuration Status", + "isPrintf": true + }, + { + "expr": "Endpoint Activity", + "isPrintf": true + }, + { + "expr": "EP0 OUT", + "isPrintf": true + }, + { + "expr": "EP0 IN", + "isPrintf": true + }, + { + "expr": "EP1 OUT", + "isPrintf": true + }, + { + "expr": "EP1 IN", + "isPrintf": true + }, + { + "expr": "EP2 OUT", + "isPrintf": true + }, + { + "expr": "EP2 IN", + "isPrintf": true + }, + { + "expr": "EP3 OUT", + "isPrintf": true + }, + { + "expr": "EP3 IN", + "isPrintf": true + }, + { + "expr": "EP4 OUT", + "isPrintf": true + }, + { + "expr": "EP4 IN", + "isPrintf": true + }, + { + "expr": "EP5 OUT", + "isPrintf": true + }, + { + "expr": "EP5 IN", + "isPrintf": true + }, + { + "expr": "EP6 OUT", + "isPrintf": true + }, + { + "expr": "EP6 IN", + "isPrintf": true + }, + { + "expr": "EP7 OUT", + "isPrintf": true + }, + { + "expr": "EP7 IN", + "isPrintf": true + }, + { + "expr": "EP8 OUT", + "isPrintf": true + }, + { + "expr": "EP8 IN", + "isPrintf": true + }, + { + "expr": "EP9 OUT", + "isPrintf": true + }, + { + "expr": "EP9 IN", + "isPrintf": true + }, + { + "expr": "EP10 OUT", + "isPrintf": true + }, + { + "expr": "EP10 IN", + "isPrintf": true + }, + { + "expr": "EP11 OUT", + "isPrintf": true + }, + { + "expr": "EP11 IN", + "isPrintf": true + }, + { + "expr": "EP12 OUT", + "isPrintf": true + }, + { + "expr": "EP12 IN", + "isPrintf": true + }, + { + "expr": "EP13 OUT", + "isPrintf": true + }, + { + "expr": "EP13 IN", + "isPrintf": true + }, + { + "expr": "EP14 OUT", + "isPrintf": true + }, + { + "expr": "EP14 IN", + "isPrintf": true + }, + { + "expr": "EP15 OUT", + "isPrintf": true + }, + { + "expr": "EP15 IN", + "isPrintf": true + }, + { + "expr": "Custom Class 0", + "isPrintf": true + }, + { + "expr": "Audio Device Class 0", + "isPrintf": true + }, + { + "expr": "Speaker", + "isPrintf": true + }, + { + "expr": "Playback", + "isPrintf": true + }, + { + "expr": "Mute", + "isPrintf": true + }, + { + "expr": "Volume", + "isPrintf": true + }, + { + "expr": "Volume Left", + "isPrintf": true + }, + { + "expr": "Volume Right", + "isPrintf": true + }, + { + "expr": "Microphone", + "isPrintf": true + }, + { + "expr": "Recording", + "isPrintf": true + }, + { + "expr": "Communication Device Class 0", + "isPrintf": true + }, + { + "expr": "Human Interface Device 0", + "isPrintf": true + }, + { + "expr": "Mass Storage Device 0", + "isPrintf": true + }, + { + "expr": "LUN0", + "isPrintf": true + }, + { + "expr": "Media Size", + "isPrintf": true + }, + { + "expr": "LUN1", + "isPrintf": true + }, + { + "expr": "LUN2", + "isPrintf": true + }, + { + "expr": "LUN3", + "isPrintf": true + }, + { + "expr": "Host 0", + "isPrintf": true + }, + { + "expr": "Pipes", + "isPrintf": true + }, + { + "expr": "Device %d[usbh0_disp_i]", + "isPrintf": true + }, + { + "expr": "Enumerated Speed", + "isPrintf": true + }, + { + "expr": "Class", + "isPrintf": true + }, + { + "expr": "Device %d[usbh1_disp_i]", + "isPrintf": true + }, + { + "expr": "Device %d[usbh2_disp_i]", + "isPrintf": true + }, + { + "expr": "Device %d[usbh3_disp_i]", + "isPrintf": true + }, + { + "expr": "Initialize", + "isPrintf": true + }, + { + "expr": "InitializeFailed", + "isPrintf": true + }, + { + "expr": "Uninitialize", + "isPrintf": true + }, + { + "expr": "UninitializeFailed", + "isPrintf": true + }, + { + "expr": "ConnectFailed", + "isPrintf": true + }, + { + "expr": "Disconnect", + "isPrintf": true + }, + { + "expr": "DisconnectFailed", + "isPrintf": true + }, + { + "expr": "Configured", + "isPrintf": true + }, + { + "expr": "GetState", + "isPrintf": true + }, + { + "expr": "SetSerialNumber", + "isPrintf": true + }, + { + "expr": "SetSerialNumberFailed", + "isPrintf": true + }, + { + "expr": "EndpointRead", + "isPrintf": true + }, + { + "expr": "EndpointReadFailed", + "isPrintf": true + }, + { + "expr": "EndpointReadGetResult", + "isPrintf": true + }, + { + "expr": "EndpointWrite", + "isPrintf": true + }, + { + "expr": "EndpointWriteFailed", + "isPrintf": true + }, + { + "expr": "EndpointWriteGetResult", + "isPrintf": true + }, + { + "expr": "EndpointStall", + "isPrintf": true + }, + { + "expr": "EndpointStallFailed", + "isPrintf": true + }, + { + "expr": "EndpointAbort", + "isPrintf": true + }, + { + "expr": "EndpointAbortFailed", + "isPrintf": true + }, + { + "expr": "ConfigureEp0", + "isPrintf": true + }, + { + "expr": "ConfigureEp0Failed", + "isPrintf": true + }, + { + "expr": "ReadSetupPacket", + "isPrintf": true + }, + { + "expr": "ReadSetupPacketFailed", + "isPrintf": true + }, + { + "expr": "ReqGetStatusFailed", + "isPrintf": true + }, + { + "expr": "ReqSetClrFeatureFailed", + "isPrintf": true + }, + { + "expr": "ReqSetAddressFailed", + "isPrintf": true + }, + { + "expr": "ReqGetDescriptorFailed", + "isPrintf": true + }, + { + "expr": "ReqGetMSDescriptorFailed", + "isPrintf": true + }, + { + "expr": "ReqGetConfigurationFailed", + "isPrintf": true + }, + { + "expr": "ReqSetConfigurationFailed", + "isPrintf": true + }, + { + "expr": "ReqGetInterfaceFailed", + "isPrintf": true + }, + { + "expr": "ReqSetInterfaceFailed", + "isPrintf": true + }, + { + "expr": "OnInitialize", + "isPrintf": true + }, + { + "expr": "OnUninitialize", + "isPrintf": true + }, + { + "expr": "OnVbusChanged", + "isPrintf": true + }, + { + "expr": "OnReset", + "isPrintf": true + }, + { + "expr": "OnHighSpeedActivated", + "isPrintf": true + }, + { + "expr": "OnSuspended", + "isPrintf": true + }, + { + "expr": "OnResumed", + "isPrintf": true + }, + { + "expr": "OnConfigurationChanged", + "isPrintf": true + }, + { + "expr": "OnEnableRemoteWakeup", + "isPrintf": true + }, + { + "expr": "OnDisableRemoteWakeup", + "isPrintf": true + }, + { + "expr": "OnSetupPacketReceived", + "isPrintf": true + }, + { + "expr": "OnSetupPacketReceivedDetail", + "isPrintf": true + }, + { + "expr": "OnSetupPacketProcessed", + "isPrintf": true + }, + { + "expr": "OnSetupPacketProcessedDetail", + "isPrintf": true + }, + { + "expr": "OnOutDataReceived", + "isPrintf": true + }, + { + "expr": "OnInDataSent", + "isPrintf": true + }, + { + "expr": "PowerControl", + "isPrintf": true + }, + { + "expr": "PowerControlFailed", + "isPrintf": true + }, + { + "expr": "DeviceConnect", + "isPrintf": true + }, + { + "expr": "DeviceConnectFailed", + "isPrintf": true + }, + { + "expr": "DeviceDisconnect", + "isPrintf": true + }, + { + "expr": "DeviceDisconnectFailed", + "isPrintf": true + }, + { + "expr": "DeviceGetState", + "isPrintf": true + }, + { + "expr": "DeviceRemoteWakeup", + "isPrintf": true + }, + { + "expr": "DeviceRemoteWakeupFailed", + "isPrintf": true + }, + { + "expr": "DeviceSetAddress", + "isPrintf": true + }, + { + "expr": "DeviceSetAddressFailed", + "isPrintf": true + }, + { + "expr": "EndpointConfigure", + "isPrintf": true + }, + { + "expr": "EndpointConfigureFailed", + "isPrintf": true + }, + { + "expr": "EndpointUnconfigure", + "isPrintf": true + }, + { + "expr": "EndpointUnconfigureFailed", + "isPrintf": true + }, + { + "expr": "EndpointTransfer", + "isPrintf": true + }, + { + "expr": "EndpointTransferFailed", + "isPrintf": true + }, + { + "expr": "EndpointTransferGetResult", + "isPrintf": true + }, + { + "expr": "EndpointTransferAbort", + "isPrintf": true + }, + { + "expr": "EndpointTransferAbortFailed", + "isPrintf": true + }, + { + "expr": "GetFrameNumber", + "isPrintf": true + }, + { + "expr": "OnSignalDeviceEvent", + "isPrintf": true + }, + { + "expr": "OnSignalEndpointEvent", + "isPrintf": true + }, + { + "expr": "CC_OnInitialize", + "isPrintf": true + }, + { + "expr": "CC_OnUninitialize", + "isPrintf": true + }, + { + "expr": "CC_OnReset", + "isPrintf": true + }, + { + "expr": "CC_OnEndpointStart", + "isPrintf": true + }, + { + "expr": "CC_OnEndpointStop", + "isPrintf": true + }, + { + "expr": "CC_OnEndpoint0SetupPacketReceived", + "isPrintf": true + }, + { + "expr": "CC_OnEndpoint0SetupPacketReceivedDetail", + "isPrintf": true + }, + { + "expr": "CC_OnEndpoint0SetupPacketProcessed", + "isPrintf": true + }, + { + "expr": "CC_OnEndpoint0SetupPacketProcessedDetail", + "isPrintf": true + }, + { + "expr": "CC_OnEndpoint0OutDataReceived", + "isPrintf": true + }, + { + "expr": "CC_OnEndpoint0InDataSent", + "isPrintf": true + }, + { + "expr": "CC_OnEndpointmEvent", + "isPrintf": true + }, + { + "expr": "Reset", + "isPrintf": true + }, + { + "expr": "ResetFailed", + "isPrintf": true + }, + { + "expr": "OnPlayStart", + "isPrintf": true + }, + { + "expr": "OnPlayStop", + "isPrintf": true + }, + { + "expr": "OnSetSpeakerMute", + "isPrintf": true + }, + { + "expr": "OnSetSpeakerVolume", + "isPrintf": true + }, + { + "expr": "OnRecordStart", + "isPrintf": true + }, + { + "expr": "OnRecordStop", + "isPrintf": true + }, + { + "expr": "OnSetMicrophoneMute", + "isPrintf": true + }, + { + "expr": "OnSetMicrophoneVolume", + "isPrintf": true + }, + { + "expr": "OnReceivedSamples", + "isPrintf": true + }, + { + "expr": "SetSpeakerVolumeRange", + "isPrintf": true + }, + { + "expr": "SetSpeakerVolumeRangeFailed", + "isPrintf": true + }, + { + "expr": "SetMicrophoneVolumeRange", + "isPrintf": true + }, + { + "expr": "SetMicrophoneVolumeRangeFailed", + "isPrintf": true + }, + { + "expr": "ReceivedSamplesAvailable", + "isPrintf": true + }, + { + "expr": "WrittenSamplesPending", + "isPrintf": true + }, + { + "expr": "ReadSamples", + "isPrintf": true + }, + { + "expr": "ReadSamplesFailed", + "isPrintf": true + }, + { + "expr": "WriteSamples", + "isPrintf": true + }, + { + "expr": "WriteSamplesFailed", + "isPrintf": true + }, + { + "expr": "ACM_OnInitialize", + "isPrintf": true + }, + { + "expr": "ACM_OnUninitialize", + "isPrintf": true + }, + { + "expr": "ACM_OnReset", + "isPrintf": true + }, + { + "expr": "ACM_OnSendEncapsulatedCommand", + "isPrintf": true + }, + { + "expr": "ACM_OnGetEncapsulatedResponse", + "isPrintf": true + }, + { + "expr": "ACM_OnSetLineCoding", + "isPrintf": true + }, + { + "expr": "ACM_OnGetLineCoding", + "isPrintf": true + }, + { + "expr": "ACM_OnSetControlLineState", + "isPrintf": true + }, + { + "expr": "ACM_ReadData", + "isPrintf": true + }, + { + "expr": "ACM_ReadDataFailed", + "isPrintf": true + }, + { + "expr": "ACM_WriteData", + "isPrintf": true + }, + { + "expr": "ACM_WriteDataFailed", + "isPrintf": true + }, + { + "expr": "ACM_GetChar", + "isPrintf": true + }, + { + "expr": "ACM_PutChar", + "isPrintf": true + }, + { + "expr": "ACM_DataAvailable", + "isPrintf": true + }, + { + "expr": "ACM_DataAvailableFailed", + "isPrintf": true + }, + { + "expr": "ACM_Notify_SerialState", + "isPrintf": true + }, + { + "expr": "ACM_Notify_SerialStateFailed", + "isPrintf": true + }, + { + "expr": "NCM_OnInitialize", + "isPrintf": true + }, + { + "expr": "NCM_OnUninitialize", + "isPrintf": true + }, + { + "expr": "NCM_OnReset", + "isPrintf": true + }, + { + "expr": "NCM_OnStart", + "isPrintf": true + }, + { + "expr": "NCM_OnStop", + "isPrintf": true + }, + { + "expr": "NCM_OnSetEthMulticastFilters", + "isPrintf": true + }, + { + "expr": "NCM_OnSetEthPowerMgmtPatFilter", + "isPrintf": true + }, + { + "expr": "NCM_OnGetEthPowerMgmtPatFilter", + "isPrintf": true + }, + { + "expr": "NCM_OnSetEthernetPacketFilter", + "isPrintf": true + }, + { + "expr": "NCM_OnGetEthernetStatistic", + "isPrintf": true + }, + { + "expr": "NCM_OnGetNtbParameters", + "isPrintf": true + }, + { + "expr": "NCM_OnGetNetAddress", + "isPrintf": true + }, + { + "expr": "NCM_OnSetNetAddress", + "isPrintf": true + }, + { + "expr": "NCM_OnGetNtbFormat", + "isPrintf": true + }, + { + "expr": "NCM_OnSetNtbFormat", + "isPrintf": true + }, + { + "expr": "NCM_OnGetNtbInputSize", + "isPrintf": true + }, + { + "expr": "NCM_OnSetNtbInputSize", + "isPrintf": true + }, + { + "expr": "NCM_OnGetMaxDatagramSize", + "isPrintf": true + }, + { + "expr": "NCM_OnSetMaxDatagramSize", + "isPrintf": true + }, + { + "expr": "NCM_OnGetCrcMode", + "isPrintf": true + }, + { + "expr": "NCM_OnSetCrcMode", + "isPrintf": true + }, + { + "expr": "NCM_OnNTB_IN_Sent", + "isPrintf": true + }, + { + "expr": "NCM_OnNTB_OUT_Received", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_Initialize", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_InitializeFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_CreateNDP", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_CreateNDPFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_WriteDatagram", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_WriteDatagramFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_Send", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_SendFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_IsSent", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_IsSentFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_IsReceived", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_IsReceivedFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_Release", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_ReleaseFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_ProcessNDP", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_ProcessNDPFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_GetDatagramSize", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_ReadDatagram", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_ReadDatagramFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_RawSend", + "isPrintf": true + }, + { + "expr": "NCM_NTB_IN_RawSendFailed", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_RawGetSize", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_RawReceive", + "isPrintf": true + }, + { + "expr": "NCM_NTB_OUT_RawReceiveFailed", + "isPrintf": true + }, + { + "expr": "NCM_Notify_NetworkConnection", + "isPrintf": true + }, + { + "expr": "NCM_Notify_NetworkConnectionFailed", + "isPrintf": true + }, + { + "expr": "NCM_Notify_ConnectionSpeedChange", + "isPrintf": true + }, + { + "expr": "NCM_Notify_ConnectionSpeedChangeFailed", + "isPrintf": true + }, + { + "expr": "ACM_Notify_ResponseAvailable", + "isPrintf": true + }, + { + "expr": "ACM_Notify_ResponseAvailableFailed", + "isPrintf": true + }, + { + "expr": "GetReport", + "isPrintf": true + }, + { + "expr": "GetReportFailed", + "isPrintf": true + }, + { + "expr": "SetReport", + "isPrintf": true + }, + { + "expr": "SetReportFailed", + "isPrintf": true + }, + { + "expr": "GetIdle", + "isPrintf": true + }, + { + "expr": "GetIdleFailed", + "isPrintf": true + }, + { + "expr": "SetIdle", + "isPrintf": true + }, + { + "expr": "SetIdleFailed", + "isPrintf": true + }, + { + "expr": "GetProtocol", + "isPrintf": true + }, + { + "expr": "GetProtocolFailed", + "isPrintf": true + }, + { + "expr": "SetProtocol", + "isPrintf": true + }, + { + "expr": "SetProtocolFailed", + "isPrintf": true + }, + { + "expr": "OnGetReport", + "isPrintf": true + }, + { + "expr": "OnGetReportFailed", + "isPrintf": true + }, + { + "expr": "OnSetReport", + "isPrintf": true + }, + { + "expr": "OnSetReportFailed", + "isPrintf": true + }, + { + "expr": "OnGetProtocol", + "isPrintf": true + }, + { + "expr": "OnGetProtocolFailed", + "isPrintf": true + }, + { + "expr": "OnSetProtocol", + "isPrintf": true + }, + { + "expr": "OnSetProtocolFailed", + "isPrintf": true + }, + { + "expr": "GetReportTrigger", + "isPrintf": true + }, + { + "expr": "GetReportTriggerFailed", + "isPrintf": true + }, + { + "expr": "BOMSReset", + "isPrintf": true + }, + { + "expr": "BOMSResetFailed", + "isPrintf": true + }, + { + "expr": "GetMaxLUN", + "isPrintf": true + }, + { + "expr": "GetMaxLUNFailed", + "isPrintf": true + }, + { + "expr": "TestUnitReady", + "isPrintf": true + }, + { + "expr": "RequestSense", + "isPrintf": true + }, + { + "expr": "Inquiry", + "isPrintf": true + }, + { + "expr": "StartStopUnit", + "isPrintf": true + }, + { + "expr": "MediaRemoval", + "isPrintf": true + }, + { + "expr": "ModeSense", + "isPrintf": true + }, + { + "expr": "ReadFormatCapacities", + "isPrintf": true + }, + { + "expr": "ReadCapacity", + "isPrintf": true + }, + { + "expr": "Read", + "isPrintf": true + }, + { + "expr": "ReadDone", + "isPrintf": true + }, + { + "expr": "Write", + "isPrintf": true + }, + { + "expr": "WriteDone", + "isPrintf": true + }, + { + "expr": "Verify", + "isPrintf": true + }, + { + "expr": "VerifyDone", + "isPrintf": true + }, + { + "expr": "SyncCache", + "isPrintf": true + }, + { + "expr": "UnsupportedCommand", + "isPrintf": true + }, + { + "expr": "OnGetCacheInfo", + "isPrintf": true + }, + { + "expr": "OnGetCacheInfoFailed", + "isPrintf": true + }, + { + "expr": "OnGetMediaCapacity", + "isPrintf": true + }, + { + "expr": "OnGetMediaCapacityFailed", + "isPrintf": true + }, + { + "expr": "OnRead", + "isPrintf": true + }, + { + "expr": "OnReadFailed", + "isPrintf": true + }, + { + "expr": "OnWrite", + "isPrintf": true + }, + { + "expr": "OnWriteFailed", + "isPrintf": true + }, + { + "expr": "OnCheckMedia", + "isPrintf": true + }, + { + "expr": "PortSuspend", + "isPrintf": true + }, + { + "expr": "PortSuspendFailed", + "isPrintf": true + }, + { + "expr": "PortResume", + "isPrintf": true + }, + { + "expr": "PortResumeFailed", + "isPrintf": true + }, + { + "expr": "DeviceGetController", + "isPrintf": true + }, + { + "expr": "DeviceGetPort", + "isPrintf": true + }, + { + "expr": "DeviceGetStatus", + "isPrintf": true + }, + { + "expr": "DeviceGetSpeed", + "isPrintf": true + }, + { + "expr": "DeviceGetAddress", + "isPrintf": true + }, + { + "expr": "DeviceGetVID", + "isPrintf": true + }, + { + "expr": "DeviceGetPID", + "isPrintf": true + }, + { + "expr": "OnNotify", + "isPrintf": true + }, + { + "expr": "PipeCreate", + "isPrintf": true + }, + { + "expr": "PipeCreateFailed", + "isPrintf": true + }, + { + "expr": "PipeUpdate", + "isPrintf": true + }, + { + "expr": "PipeUpdateFailed", + "isPrintf": true + }, + { + "expr": "PipeDelete", + "isPrintf": true + }, + { + "expr": "PipeDeleteFailed", + "isPrintf": true + }, + { + "expr": "PipeReset", + "isPrintf": true + }, + { + "expr": "PipeResetFailed", + "isPrintf": true + }, + { + "expr": "PipeReceive", + "isPrintf": true + }, + { + "expr": "PipeReceiveFailed", + "isPrintf": true + }, + { + "expr": "PipeReceiveGetResult", + "isPrintf": true + }, + { + "expr": "PipeSend", + "isPrintf": true + }, + { + "expr": "PipeSendFailed", + "isPrintf": true + }, + { + "expr": "PipeSendGetResult", + "isPrintf": true + }, + { + "expr": "PipeAbort", + "isPrintf": true + }, + { + "expr": "PipeAbortFailed", + "isPrintf": true + }, + { + "expr": "ControlTransfer", + "isPrintf": true + }, + { + "expr": "ControlTransferFailed", + "isPrintf": true + }, + { + "expr": "RequestGetStatus", + "isPrintf": true + }, + { + "expr": "RequestGetStatusFailed", + "isPrintf": true + }, + { + "expr": "RequestClearFeature", + "isPrintf": true + }, + { + "expr": "RequestClearFeatureFailed", + "isPrintf": true + }, + { + "expr": "RequestSetFeature", + "isPrintf": true + }, + { + "expr": "RequestSetFeatureFailed", + "isPrintf": true + }, + { + "expr": "RequestSetAddress", + "isPrintf": true + }, + { + "expr": "RequestSetAddressFailed", + "isPrintf": true + }, + { + "expr": "RequestGetDescriptor", + "isPrintf": true + }, + { + "expr": "RequestGetDescriptorFailed", + "isPrintf": true + }, + { + "expr": "RequestSetDescriptor", + "isPrintf": true + }, + { + "expr": "RequestSetDescriptorFailed", + "isPrintf": true + }, + { + "expr": "RequestGetConfiguration", + "isPrintf": true + }, + { + "expr": "RequestGetConfigurationFailed", + "isPrintf": true + }, + { + "expr": "RequestSetConfiguration", + "isPrintf": true + }, + { + "expr": "RequestSetConfigurationFailed", + "isPrintf": true + }, + { + "expr": "RequestGetInterface", + "isPrintf": true + }, + { + "expr": "RequestGetInterfaceFailed", + "isPrintf": true + }, + { + "expr": "RequestSetInterface", + "isPrintf": true + }, + { + "expr": "RequestSetInterfaceFailed", + "isPrintf": true + }, + { + "expr": "RequestSynchFrame", + "isPrintf": true + }, + { + "expr": "RequestSynchFrameFailed", + "isPrintf": true + }, + { + "expr": "MemInitFailed", + "isPrintf": true + }, + { + "expr": "MemInit", + "isPrintf": true + }, + { + "expr": "MemUninitFailed", + "isPrintf": true + }, + { + "expr": "MemUninit", + "isPrintf": true + }, + { + "expr": "MemAllocFailed", + "isPrintf": true + }, + { + "expr": "MemAlloc", + "isPrintf": true + }, + { + "expr": "MemFreeFailed", + "isPrintf": true + }, + { + "expr": "MemFree", + "isPrintf": true + }, + { + "expr": "Engine", + "isPrintf": true + }, + { + "expr": "EngineFailed", + "isPrintf": true + }, + { + "expr": "EngineDone", + "isPrintf": true + }, + { + "expr": "DeviceGetStringDescriptorFailed", + "isPrintf": true + }, + { + "expr": "DeviceGetStringDescriptor", + "isPrintf": true + }, + { + "expr": "PortVbusOnOff", + "isPrintf": true + }, + { + "expr": "PortVbusOnOffFailed", + "isPrintf": true + }, + { + "expr": "PortReset", + "isPrintf": true + }, + { + "expr": "PortResetFailed", + "isPrintf": true + }, + { + "expr": "PortGetState", + "isPrintf": true + }, + { + "expr": "PipeModify", + "isPrintf": true + }, + { + "expr": "PipeModifyFailed", + "isPrintf": true + }, + { + "expr": "PipeTransfer", + "isPrintf": true + }, + { + "expr": "PipeTransferFailed", + "isPrintf": true + }, + { + "expr": "PipeTransferGetResult", + "isPrintf": true + }, + { + "expr": "PipeTransferAbort", + "isPrintf": true + }, + { + "expr": "PipeTransferAbortFailed", + "isPrintf": true + }, + { + "expr": "OnSignalPortEvent", + "isPrintf": true + }, + { + "expr": "OnSignalPipeEvent", + "isPrintf": true + }, + { + "expr": "GetDevice", + "isPrintf": true + }, + { + "expr": "GetStatus", + "isPrintf": true + }, + { + "expr": "OnConfigure", + "isPrintf": true + }, + { + "expr": "OnUnconfigure", + "isPrintf": true + }, + { + "expr": "ACM_Configure", + "isPrintf": true + }, + { + "expr": "ACM_ConfigureFailed", + "isPrintf": true + }, + { + "expr": "ACM_Unconfigure", + "isPrintf": true + }, + { + "expr": "ACM_UnconfigureFailed", + "isPrintf": true + }, + { + "expr": "ACM_Initialize", + "isPrintf": true + }, + { + "expr": "ACM_InitializeFailed", + "isPrintf": true + }, + { + "expr": "ACM_Uninitialize", + "isPrintf": true + }, + { + "expr": "ACM_UninitializeFailed", + "isPrintf": true + }, + { + "expr": "ACM_GetDevice", + "isPrintf": true + }, + { + "expr": "ACM_GetStatus", + "isPrintf": true + }, + { + "expr": "ACM_Send", + "isPrintf": true + }, + { + "expr": "ACM_SendFailed", + "isPrintf": true + }, + { + "expr": "ACM_SendDone", + "isPrintf": true + }, + { + "expr": "ACM_GetTxCount", + "isPrintf": true + }, + { + "expr": "ACM_Receive", + "isPrintf": true + }, + { + "expr": "ACM_ReceiveFailed", + "isPrintf": true + }, + { + "expr": "ACM_ReceiveDone", + "isPrintf": true + }, + { + "expr": "ACM_GetRxCount", + "isPrintf": true + }, + { + "expr": "ACM_SetLineCoding", + "isPrintf": true + }, + { + "expr": "ACM_SetLineCodingFailed", + "isPrintf": true + }, + { + "expr": "ACM_GetLineCodingFailed", + "isPrintf": true + }, + { + "expr": "ACM_GetLineCoding", + "isPrintf": true + }, + { + "expr": "ACM_SetControlLineState", + "isPrintf": true + }, + { + "expr": "ACM_SetControlLineStateFailed", + "isPrintf": true + }, + { + "expr": "ACM_OnNotify", + "isPrintf": true + }, + { + "expr": "ACM_SendBreak", + "isPrintf": true + }, + { + "expr": "ACM_SendBreakFailed", + "isPrintf": true + }, + { + "expr": "ACM_AbortSend", + "isPrintf": true + }, + { + "expr": "ACM_AbortSendFailed", + "isPrintf": true + }, + { + "expr": "ACM_AbortSendDone", + "isPrintf": true + }, + { + "expr": "ACM_AbortReceive", + "isPrintf": true + }, + { + "expr": "ACM_AbortReceiveFailed", + "isPrintf": true + }, + { + "expr": "ACM_AbortReceiveDone", + "isPrintf": true + }, + { + "expr": "Configure", + "isPrintf": true + }, + { + "expr": "ConfigureFailed", + "isPrintf": true + }, + { + "expr": "Unconfigure", + "isPrintf": true + }, + { + "expr": "UnconfigureFailed", + "isPrintf": true + }, + { + "expr": "ReadFailed", + "isPrintf": true + }, + { + "expr": "WriteFailed", + "isPrintf": true + }, + { + "expr": "GetKeyboardKey", + "isPrintf": true + }, + { + "expr": "GetKeyboardKeyDone", + "isPrintf": true + }, + { + "expr": "GetMouseState", + "isPrintf": true + }, + { + "expr": "GetMouseStateFailed", + "isPrintf": true + }, + { + "expr": "GetMouseStateDone", + "isPrintf": true + }, + { + "expr": "OnParseReportDescriptor", + "isPrintf": true + }, + { + "expr": "OnDataReceived", + "isPrintf": true + }, + { + "expr": "ReportOut", + "isPrintf": true + }, + { + "expr": "ReportOutFailed", + "isPrintf": true + }, + { + "expr": "ReadCapacityFailed", + "isPrintf": true + }, + { + "expr": "ReadCapacityDone", + "isPrintf": true + }, + { + "expr": "BomReset", + "isPrintf": true + }, + { + "expr": "BomResetFailed", + "isPrintf": true + }, + { + "expr": "GetMaxLun", + "isPrintf": true + }, + { + "expr": "GetMaxLunFailed", + "isPrintf": true + }, + { + "expr": "ScsiTestUnitReady", + "isPrintf": true + }, + { + "expr": "ScsiTestUnitReadyFailed", + "isPrintf": true + }, + { + "expr": "ScsiRequestSense", + "isPrintf": true + }, + { + "expr": "ScsiRequestSenseFailed", + "isPrintf": true + }, + { + "expr": "ScsiInquiry", + "isPrintf": true + }, + { + "expr": "ScsiInquiryFailed", + "isPrintf": true + }, + { + "expr": "ScsiReadFormatCapacities", + "isPrintf": true + }, + { + "expr": "ScsiReadFormatCapacitiesFailed", + "isPrintf": true + }, + { + "expr": "ScsiReadCapacity", + "isPrintf": true + }, + { + "expr": "ScsiReadCapacityFailed", + "isPrintf": true + }, + { + "expr": "ScsiRead10", + "isPrintf": true + }, + { + "expr": "ScsiRead10Failed", + "isPrintf": true + }, + { + "expr": "ScsiWrite10", + "isPrintf": true + }, + { + "expr": "ScsiWrite10Failed", + "isPrintf": true + }, + { + "expr": "Recover", + "isPrintf": true + }, + { + "expr": "RecoverFailed", + "isPrintf": true + }, + { + "expr": "usb_lib_version_major = ( usb_lib_version / 10000000U);", + "isPrintf": false + }, + { + "expr": "usb_lib_version_minor = ((usb_lib_version / 10000U ) % 1000U);", + "isPrintf": false + }, + { + "expr": "usb_lib_version_patch = ((usb_lib_version) % 10000U);", + "isPrintf": false + }, + { + "expr": "usbd0_ep1_out_en = (usbd0_data.endpoint_mask >> 1) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep2_out_en = (usbd0_data.endpoint_mask >> 2) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep3_out_en = (usbd0_data.endpoint_mask >> 3) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep4_out_en = (usbd0_data.endpoint_mask >> 4) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep5_out_en = (usbd0_data.endpoint_mask >> 5) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep6_out_en = (usbd0_data.endpoint_mask >> 6) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep7_out_en = (usbd0_data.endpoint_mask >> 7) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep8_out_en = (usbd0_data.endpoint_mask >> 8) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep9_out_en = (usbd0_data.endpoint_mask >> 9) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep10_out_en = (usbd0_data.endpoint_mask >> 10) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep11_out_en = (usbd0_data.endpoint_mask >> 11) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep12_out_en = (usbd0_data.endpoint_mask >> 12) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep13_out_en = (usbd0_data.endpoint_mask >> 13) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep14_out_en = (usbd0_data.endpoint_mask >> 14) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep15_out_en = (usbd0_data.endpoint_mask >> 15) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep1_in_en = (usbd0_data.endpoint_mask >> 17) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep2_in_en = (usbd0_data.endpoint_mask >> 18) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep3_in_en = (usbd0_data.endpoint_mask >> 19) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep4_in_en = (usbd0_data.endpoint_mask >> 20) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep5_in_en = (usbd0_data.endpoint_mask >> 21) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep6_in_en = (usbd0_data.endpoint_mask >> 22) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep7_in_en = (usbd0_data.endpoint_mask >> 23) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep8_in_en = (usbd0_data.endpoint_mask >> 24) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep9_in_en = (usbd0_data.endpoint_mask >> 25) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep10_in_en = (usbd0_data.endpoint_mask >> 26) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep11_in_en = (usbd0_data.endpoint_mask >> 27) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep12_in_en = (usbd0_data.endpoint_mask >> 28) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep13_in_en = (usbd0_data.endpoint_mask >> 29) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep14_in_en = (usbd0_data.endpoint_mask >> 30) & 1;", + "isPrintf": false + }, + { + "expr": "usbd0_ep15_in_en = (usbd0_data.endpoint_mask >> 31) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep1_out_en = (usbd1_data.endpoint_mask >> 1) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep2_out_en = (usbd1_data.endpoint_mask >> 2) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep3_out_en = (usbd1_data.endpoint_mask >> 3) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep4_out_en = (usbd1_data.endpoint_mask >> 4) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep5_out_en = (usbd1_data.endpoint_mask >> 5) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep6_out_en = (usbd1_data.endpoint_mask >> 6) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep7_out_en = (usbd1_data.endpoint_mask >> 7) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep8_out_en = (usbd1_data.endpoint_mask >> 8) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep9_out_en = (usbd1_data.endpoint_mask >> 9) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep10_out_en = (usbd1_data.endpoint_mask >> 10) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep11_out_en = (usbd1_data.endpoint_mask >> 11) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep12_out_en = (usbd1_data.endpoint_mask >> 12) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep13_out_en = (usbd1_data.endpoint_mask >> 13) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep14_out_en = (usbd1_data.endpoint_mask >> 14) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep15_out_en = (usbd1_data.endpoint_mask >> 15) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep1_in_en = (usbd1_data.endpoint_mask >> 17) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep2_in_en = (usbd1_data.endpoint_mask >> 18) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep3_in_en = (usbd1_data.endpoint_mask >> 19) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep4_in_en = (usbd1_data.endpoint_mask >> 20) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep5_in_en = (usbd1_data.endpoint_mask >> 21) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep6_in_en = (usbd1_data.endpoint_mask >> 22) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep7_in_en = (usbd1_data.endpoint_mask >> 23) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep8_in_en = (usbd1_data.endpoint_mask >> 24) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep9_in_en = (usbd1_data.endpoint_mask >> 25) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep10_in_en = (usbd1_data.endpoint_mask >> 26) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep11_in_en = (usbd1_data.endpoint_mask >> 27) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep12_in_en = (usbd1_data.endpoint_mask >> 28) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep13_in_en = (usbd1_data.endpoint_mask >> 29) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep14_in_en = (usbd1_data.endpoint_mask >> 30) & 1;", + "isPrintf": false + }, + { + "expr": "usbd1_ep15_in_en = (usbd1_data.endpoint_mask >> 31) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep1_out_en = (usbd2_data.endpoint_mask >> 1) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep2_out_en = (usbd2_data.endpoint_mask >> 2) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep3_out_en = (usbd2_data.endpoint_mask >> 3) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep4_out_en = (usbd2_data.endpoint_mask >> 4) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep5_out_en = (usbd2_data.endpoint_mask >> 5) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep6_out_en = (usbd2_data.endpoint_mask >> 6) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep7_out_en = (usbd2_data.endpoint_mask >> 7) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep8_out_en = (usbd2_data.endpoint_mask >> 8) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep9_out_en = (usbd2_data.endpoint_mask >> 9) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep10_out_en = (usbd2_data.endpoint_mask >> 10) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep11_out_en = (usbd2_data.endpoint_mask >> 11) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep12_out_en = (usbd2_data.endpoint_mask >> 12) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep13_out_en = (usbd2_data.endpoint_mask >> 13) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep14_out_en = (usbd2_data.endpoint_mask >> 14) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep15_out_en = (usbd2_data.endpoint_mask >> 15) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep1_in_en = (usbd2_data.endpoint_mask >> 17) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep2_in_en = (usbd2_data.endpoint_mask >> 18) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep3_in_en = (usbd2_data.endpoint_mask >> 19) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep4_in_en = (usbd2_data.endpoint_mask >> 20) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep5_in_en = (usbd2_data.endpoint_mask >> 21) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep6_in_en = (usbd2_data.endpoint_mask >> 22) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep7_in_en = (usbd2_data.endpoint_mask >> 23) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep8_in_en = (usbd2_data.endpoint_mask >> 24) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep9_in_en = (usbd2_data.endpoint_mask >> 25) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep10_in_en = (usbd2_data.endpoint_mask >> 26) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep11_in_en = (usbd2_data.endpoint_mask >> 27) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep12_in_en = (usbd2_data.endpoint_mask >> 28) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep13_in_en = (usbd2_data.endpoint_mask >> 29) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep14_in_en = (usbd2_data.endpoint_mask >> 30) & 1;", + "isPrintf": false + }, + { + "expr": "usbd2_ep15_in_en = (usbd2_data.endpoint_mask >> 31) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep1_out_en = (usbd3_data.endpoint_mask >> 1) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep2_out_en = (usbd3_data.endpoint_mask >> 2) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep3_out_en = (usbd3_data.endpoint_mask >> 3) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep4_out_en = (usbd3_data.endpoint_mask >> 4) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep5_out_en = (usbd3_data.endpoint_mask >> 5) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep6_out_en = (usbd3_data.endpoint_mask >> 6) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep7_out_en = (usbd3_data.endpoint_mask >> 7) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep8_out_en = (usbd3_data.endpoint_mask >> 8) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep9_out_en = (usbd3_data.endpoint_mask >> 9) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep10_out_en = (usbd3_data.endpoint_mask >> 10) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep11_out_en = (usbd3_data.endpoint_mask >> 11) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep12_out_en = (usbd3_data.endpoint_mask >> 12) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep13_out_en = (usbd3_data.endpoint_mask >> 13) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep14_out_en = (usbd3_data.endpoint_mask >> 14) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep15_out_en = (usbd3_data.endpoint_mask >> 15) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep1_in_en = (usbd3_data.endpoint_mask >> 17) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep2_in_en = (usbd3_data.endpoint_mask >> 18) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep3_in_en = (usbd3_data.endpoint_mask >> 19) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep4_in_en = (usbd3_data.endpoint_mask >> 20) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep5_in_en = (usbd3_data.endpoint_mask >> 21) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep6_in_en = (usbd3_data.endpoint_mask >> 22) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep7_in_en = (usbd3_data.endpoint_mask >> 23) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep8_in_en = (usbd3_data.endpoint_mask >> 24) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep9_in_en = (usbd3_data.endpoint_mask >> 25) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep10_in_en = (usbd3_data.endpoint_mask >> 26) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep11_in_en = (usbd3_data.endpoint_mask >> 27) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep12_in_en = (usbd3_data.endpoint_mask >> 28) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep13_in_en = (usbd3_data.endpoint_mask >> 29) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep14_in_en = (usbd3_data.endpoint_mask >> 30) & 1;", + "isPrintf": false + }, + { + "expr": "usbd3_ep15_in_en = (usbd3_data.endpoint_mask >> 31) & 1;", + "isPrintf": false + }, + { + "expr": "usbd_cc_num = usbd_cc_num_hw;", + "isPrintf": false + }, + { + "expr": "usbd_cc_num = 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cc0_en = (usbd_cc[0].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cc0_en = (usbd_cc[0].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cc0_en = (usbd_cc[0].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cc0_en = (usbd_cc[0].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd_cc[0].if_num = (usbd_cc[0].if0_en + usbd_cc[0].if1_en + usbd_cc[0].if2_en + usbd_cc[0].if3_en);", + "isPrintf": false + }, + { + "expr": "usbd0_cc1_en = (usbd_cc[1].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cc1_en = (usbd_cc[1].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cc1_en = (usbd_cc[1].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cc1_en = (usbd_cc[1].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cc2_en = (usbd_cc[2].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cc2_en = (usbd_cc[2].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cc2_en = (usbd_cc[2].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cc2_en = (usbd_cc[2].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cc3_en = (usbd_cc[3].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cc3_en = (usbd_cc[3].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cc3_en = (usbd_cc[3].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cc3_en = (usbd_cc[3].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd_adc_num = usbd_adc_num_hw;", + "isPrintf": false + }, + { + "expr": "usbd_adc_num = 0;", + "isPrintf": false + }, + { + "expr": "usbd0_adc0_en = (usbd_adc[0].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_adc0_en = (usbd_adc[0].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_adc0_en = (usbd_adc[0].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_adc0_en = (usbd_adc[0].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_adc1_en = (usbd_adc[1].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_adc1_en = (usbd_adc[1].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_adc1_en = (usbd_adc[1].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_adc1_en = (usbd_adc[1].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_adc2_en = (usbd_adc[2].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_adc2_en = (usbd_adc[2].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_adc2_en = (usbd_adc[2].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_adc2_en = (usbd_adc[2].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_adc3_en = (usbd_adc[3].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_adc3_en = (usbd_adc[3].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_adc3_en = (usbd_adc[3].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_adc3_en = (usbd_adc[3].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd_cdc_num = usbd_cdc_num_hw;", + "isPrintf": false + }, + { + "expr": "usbd_cdc_num = 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc0_en = (usbd_cdc[0].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc0_en = (usbd_cdc[0].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc0_en = (usbd_cdc[0].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc0_en = (usbd_cdc[0].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc1_en = (usbd_cdc[1].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc1_en = (usbd_cdc[1].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc1_en = (usbd_cdc[1].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc1_en = (usbd_cdc[1].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc2_en = (usbd_cdc[2].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc2_en = (usbd_cdc[2].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc2_en = (usbd_cdc[2].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc2_en = (usbd_cdc[2].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc3_en = (usbd_cdc[3].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc3_en = (usbd_cdc[3].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc3_en = (usbd_cdc[3].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc3_en = (usbd_cdc[3].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc4_en = (usbd_cdc[4].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc4_en = (usbd_cdc[4].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc4_en = (usbd_cdc[4].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc4_en = (usbd_cdc[4].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc5_en = (usbd_cdc[5].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc5_en = (usbd_cdc[5].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc5_en = (usbd_cdc[5].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc5_en = (usbd_cdc[5].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc6_en = (usbd_cdc[6].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc6_en = (usbd_cdc[6].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc6_en = (usbd_cdc[6].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc6_en = (usbd_cdc[6].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_cdc7_en = (usbd_cdc[7].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_cdc7_en = (usbd_cdc[7].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_cdc7_en = (usbd_cdc[7].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_cdc7_en = (usbd_cdc[7].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd_hid_num = usbd_hid_num_hw;", + "isPrintf": false + }, + { + "expr": "usbd_hid_num = 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid0_en = (usbd_hid[0].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid0_en = (usbd_hid[0].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid0_en = (usbd_hid[0].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid0_en = (usbd_hid[0].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid1_en = (usbd_hid[1].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid1_en = (usbd_hid[1].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid1_en = (usbd_hid[1].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid1_en = (usbd_hid[1].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid2_en = (usbd_hid[2].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid2_en = (usbd_hid[2].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid2_en = (usbd_hid[2].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid2_en = (usbd_hid[2].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid3_en = (usbd_hid[3].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid3_en = (usbd_hid[3].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid3_en = (usbd_hid[3].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid3_en = (usbd_hid[3].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid4_en = (usbd_hid[4].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid4_en = (usbd_hid[4].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid4_en = (usbd_hid[4].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid4_en = (usbd_hid[4].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid5_en = (usbd_hid[5].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid5_en = (usbd_hid[5].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid5_en = (usbd_hid[5].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid5_en = (usbd_hid[5].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid6_en = (usbd_hid[6].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid6_en = (usbd_hid[6].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid6_en = (usbd_hid[6].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid6_en = (usbd_hid[6].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_hid7_en = (usbd_hid[7].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_hid7_en = (usbd_hid[7].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_hid7_en = (usbd_hid[7].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_hid7_en = (usbd_hid[7].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd_msc_num = usbd_msc_num_hw;", + "isPrintf": false + }, + { + "expr": "usbd_msc_num = 0;", + "isPrintf": false + }, + { + "expr": "usbd0_msc0_en = (usbd_msc[0].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_msc0_en = (usbd_msc[0].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_msc0_en = (usbd_msc[0].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_msc0_en = (usbd_msc[0].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_msc1_en = (usbd_msc[1].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_msc1_en = (usbd_msc[1].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_msc1_en = (usbd_msc[1].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_msc1_en = (usbd_msc[1].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_msc2_en = (usbd_msc[2].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_msc2_en = (usbd_msc[2].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_msc2_en = (usbd_msc[2].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_msc2_en = (usbd_msc[2].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd0_msc3_en = (usbd_msc[3].dev_num == 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd1_msc3_en = (usbd_msc[3].dev_num == 1) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd2_msc3_en = (usbd_msc[3].dev_num == 2) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd3_msc3_en = (usbd_msc[3].dev_num == 3) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size = usbd_msc_data[usb_i].lun0_block_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size *= usbd_msc_data[usb_i].lun0_block_count;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size_dbl = usbd_msc_data[usb_i].lun0_media_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size = usbd_msc_data[usb_i].lun1_block_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size *= usbd_msc_data[usb_i].lun1_block_count;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size_dbl = usbd_msc_data[usb_i].lun1_media_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size = usbd_msc_data[usb_i].lun2_block_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size *= usbd_msc_data[usb_i].lun2_block_count;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size_dbl = usbd_msc_data[usb_i].lun2_media_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size = usbd_msc_data[usb_i].lun3_block_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size *= usbd_msc_data[usb_i].lun3_block_count;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size_dbl = usbd_msc_data[usb_i].lun3_media_size;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size_xB = usbd_msc_data[usb_i].lun0_media_size_dbl / (1024); usbd_msc_data[usb_i].lun0_media_size_x = 1;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size_xB = usbd_msc_data[usb_i].lun0_media_size_dbl / (1024*1024); usbd_msc_data[usb_i].lun0_media_size_x = 2;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun0_media_size_xB = usbd_msc_data[usb_i].lun0_media_size_dbl / (1024*1024*1024); usbd_msc_data[usb_i].lun0_media_size_x = 3;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size_xB = usbd_msc_data[usb_i].lun1_media_size_dbl / (1024); usbd_msc_data[usb_i].lun1_media_size_x = 1;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size_xB = usbd_msc_data[usb_i].lun1_media_size_dbl / (1024*1024); usbd_msc_data[usb_i].lun1_media_size_x = 2;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun1_media_size_xB = usbd_msc_data[usb_i].lun1_media_size_dbl / (1024*1024*1024); usbd_msc_data[usb_i].lun1_media_size_x = 3;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size_xB = usbd_msc_data[usb_i].lun2_media_size_dbl / (1024); usbd_msc_data[usb_i].lun2_media_size_x = 1;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size_xB = usbd_msc_data[usb_i].lun2_media_size_dbl / (1024*1024); usbd_msc_data[usb_i].lun2_media_size_x = 2;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun2_media_size_xB = usbd_msc_data[usb_i].lun2_media_size_dbl / (1024*1024*1024); usbd_msc_data[usb_i].lun2_media_size_x = 3;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size_xB = usbd_msc_data[usb_i].lun3_media_size_dbl / (1024); usbd_msc_data[usb_i].lun3_media_size_x = 1;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size_xB = usbd_msc_data[usb_i].lun3_media_size_dbl / (1024*1024); usbd_msc_data[usb_i].lun3_media_size_x = 2;", + "isPrintf": false + }, + { + "expr": "usbd_msc_data[usb_i].lun3_media_size_xB = usbd_msc_data[usb_i].lun3_media_size_dbl / (1024*1024*1024); usbd_msc_data[usb_i].lun3_media_size_x = 3;", + "isPrintf": false + }, + { + "expr": "usbh0_pipe_used_num += (usbh0_pipe[usb_i].hw_handle != 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbh1_pipe_used_num += (usbh1_pipe[usb_i].hw_handle != 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbh2_pipe_used_num += (usbh2_pipe[usb_i].hw_handle != 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbh3_pipe_used_num += (usbh3_pipe[usb_i].hw_handle != 0) ? 1 : 0;", + "isPrintf": false + }, + { + "expr": "usbh_dev_num = usbh_dev_num_hw;", + "isPrintf": false + }, + { + "expr": "usbh_dev_num = 0;", + "isPrintf": false + }, + { + "expr": "usbh_msc_num = usbh_msc_num_hw;", + "isPrintf": false + }, + { + "expr": "usbh_msc_num = 0;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size = usbh_msc[usb_i].block_count;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size *= usbh_msc[usb_i].block_size;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size_dbl = usbh_msc[usb_i].media_size;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size_x = 0;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size_xB = usbh_msc[usb_i].media_size_dbl / (1024); usbh_msc[usb_i].media_size_x = 1;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size_xB = usbh_msc[usb_i].media_size_dbl / (1024*1024); usbh_msc[usb_i].media_size_x = 2;", + "isPrintf": false + }, + { + "expr": "usbh_msc[usb_i].media_size_xB = usbh_msc[usb_i].media_size_dbl / (1024*1024*1024); usbh_msc[usb_i].media_size_x = 3;", + "isPrintf": false + } + ] +} diff --git a/src/views/component-viewer/test/unit/component-viewer-controller.test.ts b/src/views/component-viewer/test/unit/component-viewer-controller.test.ts new file mode 100644 index 00000000..9694723e --- /dev/null +++ b/src/views/component-viewer/test/unit/component-viewer-controller.test.ts @@ -0,0 +1,251 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ComponentViewerController. + */ + +const registerTreeDataProvider = jest.fn(() => ({ dispose: jest.fn() })); + +jest.mock('vscode', () => ({ + window: { + registerTreeDataProvider, + }, +})); + +const treeProviderFactory = jest.fn(() => ({ + addGuiOut: jest.fn(), + showModelData: jest.fn(), + deleteModels: jest.fn(), + resetModelCache: jest.fn(), +})); + +jest.mock('../../component-viewer-tree-view', () => ({ + ComponentViewerTreeDataProvider: jest.fn(() => treeProviderFactory()), +})); + +const instanceFactory = jest.fn(() => ({ + readModel: jest.fn().mockResolvedValue(undefined), + update: jest.fn().mockResolvedValue(undefined), + getGuiTree: jest.fn(() => ['node']), +})); + +jest.mock('../../component-viewer-instance', () => ({ + ComponentViewerInstance: jest.fn(() => instanceFactory()), +})); + +jest.mock('../../../../debug-session', () => ({})); + +import type { ExtensionContext } from 'vscode'; +import type { GDBTargetDebugTracker } from '../../../../debug-session'; +import { ComponentViewer } from '../../component-viewer-main'; + +type TrackerCallbacks = { + onWillStopSession: (cb: (session: Session) => Promise) => { dispose: jest.Mock }; + onConnected: (cb: (session: Session) => Promise) => { dispose: jest.Mock }; + onDidChangeActiveStackItem: (cb: (item: StackItem) => Promise) => { dispose: jest.Mock }; + onDidChangeActiveDebugSession: (cb: (session: Session | undefined) => Promise) => { dispose: jest.Mock }; + onStopped: (cb: (session: { session: Session }) => Promise) => { dispose: jest.Mock }; + callbacks: Partial<{ + willStop: (session: Session) => Promise; + connected: (session: Session) => Promise; + stackItem: (item: StackItem) => Promise; + activeSession: (session: Session | undefined) => Promise; + stopped: (session: { session: Session }) => Promise; + }>; +}; + +type Session = { + session: { id: string }; + getCbuildRun: () => Promise<{ getScvdFilePaths: () => string[] } | undefined>; + refreshTimer: { onRefresh: (cb: (session: Session) => void) => void }; +}; + +type StackItem = { item: { frameId?: number } }; + +type Context = { subscriptions: Array<{ dispose: jest.Mock }> }; + +describe('ComponentViewerController', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const makeContext = (): Context => ({ subscriptions: [] }); + + const makeTracker = (): TrackerCallbacks => { + const callbacks: TrackerCallbacks['callbacks'] = {}; + return { + callbacks, + onWillStopSession: (cb) => { + callbacks.willStop = cb; + return { dispose: jest.fn() }; + }, + onConnected: (cb) => { + callbacks.connected = cb; + return { dispose: jest.fn() }; + }, + onDidChangeActiveStackItem: (cb) => { + callbacks.stackItem = cb; + return { dispose: jest.fn() }; + }, + onDidChangeActiveDebugSession: (cb) => { + callbacks.activeSession = cb; + return { dispose: jest.fn() }; + }, + onStopped: (cb) => { + callbacks.stopped = cb; + return { dispose: jest.fn() }; + }, + }; + }; + + const makeSession = (id: string, paths: string[] = []): Session => ({ + session: { id }, + getCbuildRun: async () => ({ getScvdFilePaths: () => paths }), + refreshTimer: { + onRefresh: jest.fn(), + }, + }); + + it('activates tree provider and registers tracker events', async () => { + const context = makeContext(); + const tracker = makeTracker(); + const controller = new ComponentViewer(context as unknown as ExtensionContext); + + await controller.activate(tracker as unknown as GDBTargetDebugTracker); + + expect(registerTreeDataProvider).toHaveBeenCalledWith('cmsis-debugger.componentViewer', expect.any(Object)); + expect(context.subscriptions.length).toBe(6); + }); + + it('skips reading scvd files when session or cbuild-run is missing', async () => { + const controller = new ComponentViewer(makeContext() as unknown as ExtensionContext); + const tracker = makeTracker(); + + const readScvdFiles = (controller as unknown as { readScvdFiles: (t: TrackerCallbacks, s?: Session) => Promise }).readScvdFiles.bind(controller); + + await readScvdFiles(tracker, undefined); + + const sessionNoReader: Session = { + session: { id: 's1' }, + getCbuildRun: async () => undefined, + refreshTimer: { onRefresh: jest.fn() }, + }; + await readScvdFiles(tracker, sessionNoReader); + }); + + it('skips reading when no scvd files are listed', async () => { + const controller = new ComponentViewer(makeContext() as unknown as ExtensionContext); + const tracker = makeTracker(); + const session = makeSession('s1', []); + const readScvdFiles = (controller as unknown as { readScvdFiles: (t: TrackerCallbacks, s?: Session) => Promise }).readScvdFiles.bind(controller); + + await readScvdFiles(tracker, session); + const instances = (controller as unknown as { _instances: unknown[] })._instances; + expect(instances).toEqual([]); + }); + + it('reads scvd files when active session is set', async () => { + const context = makeContext(); + const controller = new ComponentViewer(context as unknown as ExtensionContext); + const tracker = makeTracker(); + const session = makeSession('s1', ['a.scvd', 'b.scvd']); + (controller as unknown as { _activeSession?: Session })._activeSession = session; + + const readScvdFiles = (controller as unknown as { readScvdFiles: (t: TrackerCallbacks, s?: Session) => Promise }).readScvdFiles.bind(controller); + await readScvdFiles(tracker, session); + + const instances = (controller as unknown as { _instances: unknown[] })._instances; + expect(instances.length).toBe(2); + }); + + it('skips scvd instances when active session is missing', async () => { + const controller = new ComponentViewer(makeContext() as unknown as ExtensionContext); + const tracker = makeTracker(); + const session = makeSession('s1', ['a.scvd']); + + const readScvdFiles = (controller as unknown as { readScvdFiles: (t: TrackerCallbacks, s?: Session) => Promise }).readScvdFiles.bind(controller); + await readScvdFiles(tracker, session); + + const instances = (controller as unknown as { _instances: unknown[] })._instances; + expect(instances.length).toBe(0); + }); + + it('handles tracker events and updates sessions', async () => { + const context = makeContext(); + const tracker = makeTracker(); + const controller = new ComponentViewer(context as unknown as ExtensionContext); + await controller.activate(tracker as unknown as GDBTargetDebugTracker); + + const session = makeSession('s1', ['a.scvd']); + const otherSession = makeSession('s2', []); + + await tracker.callbacks.connected?.(session); + await tracker.callbacks.connected?.(session); + + const refreshCallback = (session.refreshTimer.onRefresh as jest.Mock).mock.calls[0]?.[0]; + if (refreshCallback) { + await refreshCallback(session); + await refreshCallback(otherSession); + } + + await tracker.callbacks.connected?.(otherSession); + await tracker.callbacks.activeSession?.(session); + await tracker.callbacks.activeSession?.(undefined); + + await tracker.callbacks.stackItem?.({ item: { frameId: 1 } }); + await tracker.callbacks.stackItem?.({ item: {} }); + + (controller as unknown as { _activeSession?: Session })._activeSession = session; + await tracker.callbacks.stopped?.({ session }); + await tracker.callbacks.stopped?.({ session: otherSession }); + (controller as unknown as { _activeSession?: Session })._activeSession = session; + await tracker.callbacks.willStop?.(session); + (controller as unknown as { _activeSession?: Session })._activeSession = otherSession; + await tracker.callbacks.willStop?.(session); + }); + + it('updates instances and respects semaphore and empty states', async () => { + const context = makeContext(); + const controller = new ComponentViewer(context as unknown as ExtensionContext); + const provider = treeProviderFactory(); + (controller as unknown as { _componentViewerTreeDataProvider?: typeof provider })._componentViewerTreeDataProvider = provider; + + const updateInstances = (controller as unknown as { updateInstances: () => Promise }).updateInstances.bind(controller); + + (controller as unknown as { _updateSemaphoreFlag: boolean })._updateSemaphoreFlag = true; + await updateInstances(); + + (controller as unknown as { _updateSemaphoreFlag: boolean })._updateSemaphoreFlag = false; + (controller as unknown as { _activeSession?: Session | undefined })._activeSession = undefined; + await updateInstances(); + expect(provider.deleteModels).toHaveBeenCalled(); + + (controller as unknown as { _activeSession?: Session | undefined })._activeSession = makeSession('s1'); + (controller as unknown as { _instances: unknown[] })._instances = []; + await updateInstances(); + + const instanceA = instanceFactory(); + const instanceB = instanceFactory(); + (controller as unknown as { _instances: unknown[] })._instances = [instanceA, instanceB]; + await updateInstances(); + expect(provider.resetModelCache).toHaveBeenCalled(); + expect(provider.addGuiOut).toHaveBeenCalledTimes(2); + expect(provider.showModelData).toHaveBeenCalled(); + }); +}); diff --git a/src/views/component-viewer/test/unit/component-viewer-instance.test.ts b/src/views/component-viewer/test/unit/component-viewer-instance.test.ts new file mode 100644 index 00000000..db662fc3 --- /dev/null +++ b/src/views/component-viewer/test/unit/component-viewer-instance.test.ts @@ -0,0 +1,235 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ComponentViewerInstance: happy path and guard branches. + */ + +import * as vscode from 'vscode'; +import { URI } from 'vscode-uri'; +import { parseStringPromise } from 'xml2js'; +import { ComponentViewerInstance } from '../../component-viewer-instance'; +import { ScvdComponentViewer } from '../../model/scvd-component-viewer'; +import { ScvdBase } from '../../model/scvd-base'; +import { Resolver } from '../../resolver'; +import { ScvdEvalContext } from '../../scvd-eval-context'; +import { StatementEngine } from '../../statement-engine/statement-engine'; +import { ScvdGuiTree } from '../../scvd-gui-tree'; +import type { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../../../debug-session'; + +jest.mock('vscode', () => ({ + workspace: { + fs: { + readFile: jest.fn(), + }, + }, +})); + +jest.mock('xml2js', () => ({ + parseStringPromise: jest.fn(), +})); + +jest.mock('../../model/scvd-base', () => ({ + ScvdBase: { + resetIds: jest.fn(), + }, +})); + +jest.mock('../../model/scvd-component-viewer', () => ({ + ScvdComponentViewer: jest.fn(), +})); + +jest.mock('../../resolver', () => ({ + Resolver: jest.fn(), +})); + +jest.mock('../../scvd-eval-context', () => ({ + ScvdEvalContext: jest.fn(), +})); + +jest.mock('../../statement-engine/statement-engine', () => ({ + StatementEngine: jest.fn(), +})); + +jest.mock('../../scvd-gui-tree', () => ({ + ScvdGuiTree: jest.fn(), +})); + +describe('ComponentViewerInstance', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('reads a model, initializes the engine, and updates', async () => { + const readFileMock = vscode.workspace.fs.readFile as jest.Mock; + const parseStringMock = parseStringPromise as jest.Mock; + const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + readFileMock.mockResolvedValue(Buffer.from('\n \n')); + parseStringMock.mockResolvedValue({ root: { child: {} } }); + + const readXml = jest.fn(); + const setExecutionContextAll = jest.fn(); + const configureAll = jest.fn(); + const validateAll = jest.fn(); + const calculateTypedefs = jest.fn().mockResolvedValue(undefined); + (ScvdComponentViewer as unknown as jest.Mock).mockImplementation(() => ({ + readXml, + setExecutionContextAll, + configureAll, + validateAll, + calculateTypedefs, + })); + + const resolve = jest.fn(); + (Resolver as unknown as jest.Mock).mockImplementation(() => ({ + resolve, + })); + + const init = jest.fn(); + const getExecutionContext = jest.fn().mockReturnValue({ exec: true }); + (ScvdEvalContext as unknown as jest.Mock).mockImplementation(() => ({ + init, + getExecutionContext, + })); + + const initialize = jest.fn(); + const executeAll = jest.fn().mockResolvedValue(undefined); + (StatementEngine as unknown as jest.Mock).mockImplementation(() => ({ + initialize, + executeAll, + })); + + const beginUpdate = jest.fn().mockReturnValue(7); + const finalizeUpdate = jest.fn(); + (ScvdGuiTree as unknown as jest.Mock).mockImplementation(() => ({ + children: ['child'], + beginUpdate, + finalizeUpdate, + })); + + const instance = new ComponentViewerInstance(); + const debugSession = {} as unknown as GDBTargetDebugSession; + const debugTracker = {} as unknown as GDBTargetDebugTracker; + await instance.readModel(URI.file('/tmp/example.scvd'), debugSession, debugTracker); + + expect(ScvdBase.resetIds).toHaveBeenCalled(); + expect(readXml).toHaveBeenCalled(); + expect(setExecutionContextAll).toHaveBeenCalledWith({ exec: true }); + expect(configureAll).toHaveBeenCalled(); + expect(validateAll).toHaveBeenCalledWith(true); + expect(resolve).toHaveBeenCalled(); + expect(calculateTypedefs).toHaveBeenCalled(); + expect(initialize).toHaveBeenCalled(); + expect(instance.getGuiTree()).toEqual(['child']); + + await instance.update(); + expect(beginUpdate).toHaveBeenCalled(); + expect(executeAll).toHaveBeenCalledWith(expect.any(Object)); + expect(finalizeUpdate).toHaveBeenCalledWith(7); + + const guiTree = {} as ScvdGuiTree; + await instance.executeStatements(guiTree); + expect(executeAll).toHaveBeenCalledWith(guiTree); + + consoleLog.mockRestore(); + consoleError.mockRestore(); + }); + + it('skips update and executeStatements when dependencies are missing', async () => { + const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + const instance = new ComponentViewerInstance(); + + expect(instance.getGuiTree()).toBeUndefined(); + await instance.update(); + await instance.executeStatements({} as ScvdGuiTree); + + consoleLog.mockRestore(); + }); + + it('handles XML parse failures', async () => { + const readFileMock = vscode.workspace.fs.readFile as jest.Mock; + const parseStringMock = parseStringPromise as jest.Mock; + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + readFileMock.mockResolvedValue(Buffer.from('')); + parseStringMock.mockRejectedValue(new Error('parse failed')); + + const instance = new ComponentViewerInstance(); + await instance.readModel(URI.file('/tmp/invalid.scvd'), {} as unknown as GDBTargetDebugSession, {} as unknown as GDBTargetDebugTracker); + + expect(consoleError).toHaveBeenCalled(); + consoleError.mockRestore(); + }); + + it('handles model construction failures', async () => { + const readFileMock = vscode.workspace.fs.readFile as jest.Mock; + const parseStringMock = parseStringPromise as jest.Mock; + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + readFileMock.mockResolvedValue(Buffer.from('')); + parseStringMock.mockResolvedValue({ root: {} }); + (ScvdComponentViewer as unknown as jest.Mock).mockImplementation(() => ({ + readXml: jest.fn(), + setExecutionContextAll: jest.fn(), + configureAll: jest.fn(), + validateAll: jest.fn(), + calculateTypedefs: jest.fn(), + })); + + const instance = new ComponentViewerInstance(); + const modelGetter = jest + .spyOn(instance as unknown as { model: ScvdComponentViewer | undefined }, 'model', 'get') + .mockReturnValue(undefined); + + await instance.readModel(URI.file('/tmp/model.scvd'), {} as unknown as GDBTargetDebugSession, {} as unknown as GDBTargetDebugTracker); + + expect(consoleError).toHaveBeenCalledWith('Failed to create SCVD model'); + + modelGetter.mockRestore(); + consoleError.mockRestore(); + }); + + it('rethrows file read errors', async () => { + const readFileMock = vscode.workspace.fs.readFile as jest.Mock; + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + readFileMock.mockRejectedValue(new Error('read failed')); + const instance = new ComponentViewerInstance(); + const instanceWithReader = instance as unknown as { readFileToBuffer: (filePath: URI) => Promise }; + await expect(instanceWithReader.readFileToBuffer(URI.file('/tmp/missing'))).rejects.toThrow('read failed'); + + expect(consoleError).toHaveBeenCalled(); + consoleError.mockRestore(); + }); + + it('injects line numbers and reports stats twice', () => { + const instance = new ComponentViewerInstance(); + const injectLineNumbers = (instance as unknown as { injectLineNumbers: (xml: string) => string }).injectLineNumbers; + const tagged = injectLineNumbers('\n\n'); + + expect(tagged).toContain('__line="1"'); + expect(tagged).toContain('__line="2"'); + + const first = instance.getStats('first'); + const second = instance.getStats('second'); + expect(first).toContain('Time:'); + expect(second).toContain('Mem Increase:'); + }); +}); diff --git a/src/views/component-viewer/test/unit/component-viewer-target-access.test.ts b/src/views/component-viewer/test/unit/component-viewer-target-access.test.ts new file mode 100644 index 00000000..130e084c --- /dev/null +++ b/src/views/component-viewer/test/unit/component-viewer-target-access.test.ts @@ -0,0 +1,201 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ComponentViewerTargetAccess. + */ + +import * as vscode from 'vscode'; +import { ComponentViewerTargetAccess } from '../../component-viewer-target-access'; +import { debugSessionFactory } from '../../../../__test__/vscode.factory'; +import { GDBTargetDebugSession } from '../../../../debug-session'; +import { logger } from '../../../../logger'; + +type DebugWithSession = { + activeDebugSession: vscode.DebugSession | undefined; + activeStackItem: vscode.DebugStackFrame | undefined; +}; + +function setActiveStackItem(session: vscode.DebugSession | undefined, frameId: number | undefined) { + (vscode.debug as unknown as DebugWithSession).activeStackItem = session + ? ({ session, threadId: 1, frameId } as unknown as vscode.DebugStackFrame) + : undefined; +} + +describe('ComponentViewerTargetAccess', () => { + const defaultConfig = () => ({ + name: 'test-session', + type: 'gdbtarget', + request: 'launch', + }); + + let debugSession: vscode.DebugSession; + let gdbTargetSession: GDBTargetDebugSession; + let targetAccess: ComponentViewerTargetAccess; + + beforeEach(() => { + debugSession = debugSessionFactory(defaultConfig()); + gdbTargetSession = new GDBTargetDebugSession(debugSession); + targetAccess = new ComponentViewerTargetAccess(); + targetAccess.setActiveSession(gdbTargetSession); + setActiveStackItem(undefined, undefined); + }); + + afterEach(() => { + setActiveStackItem(undefined, undefined); + (vscode.debug as unknown as DebugWithSession).activeDebugSession = undefined; + jest.restoreAllMocks(); + }); + + it('initializes session from vscode when available', () => { + (vscode.debug as unknown as DebugWithSession).activeDebugSession = debugSession; + + const instance = new ComponentViewerTargetAccess(); + + expect(instance._activeSession).toBeInstanceOf(GDBTargetDebugSession); + expect(instance._activeSession?.session).toBe(debugSession); + }); + + it('formats addresses consistently', () => { + const formatAddress = (targetAccess as unknown as { formatAddress: (addr: string | number | bigint) => string }) + .formatAddress; + + expect(formatAddress('')).toBe(''); + expect(formatAddress(' 0x1A ')).toBe('0x1A'); + expect(formatAddress('15')).toBe('0xf'); + expect(formatAddress(16)).toBe('0x10'); + expect(formatAddress(0x20n)).toBe('0x20'); + expect(formatAddress('not-a-number')).toBe('not-a-number'); + }); + + it('evaluates symbol address and handles failures', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: '0x20000000 extra' }); + setActiveStackItem(debugSession, 9); + + await expect(targetAccess.evaluateSymbolAddress('myVar')).resolves.toBe('0x20000000'); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('bad')); + + await expect(targetAccess.evaluateSymbolAddress('missing')).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate address \'missing\' - \'bad\'' + ); + }); + + it('evaluates symbol name with valid and missing results', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: '0x20000000 ' }); + setActiveStackItem(debugSession, 1); + + await expect(targetAccess.evaluateSymbolName(0x20000000)).resolves.toBe('MySymbol'); + + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: '0x20000000 ' }); + setActiveStackItem(undefined, undefined); + await expect(targetAccess.evaluateSymbolName('0x20000000')).resolves.toBe('Other'); + expect(debugSession.customRequest).toHaveBeenLastCalledWith('evaluate', { + expression: '(unsigned int*)0x20000000', + frameId: 0, + context: 'hover', + }); + + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: 'No symbol matches' }); + await expect(targetAccess.evaluateSymbolName('0x0')).resolves.toBeUndefined(); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('oops')); + await expect(targetAccess.evaluateSymbolName('0x1')).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate name \'0x1\' - \'oops\'' + ); + }); + + it('evaluates symbol context', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: 'main.c:10' }); + await expect(targetAccess.evaluateSymbolContext('0x100')).resolves.toBe('main.c:10'); + + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: 'No line information' }); + await expect(targetAccess.evaluateSymbolContext('0x100')).resolves.toBeUndefined(); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('context fail')); + await expect(targetAccess.evaluateSymbolContext('0x100')).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate context for \'0x100\' - \'context fail\'' + ); + }); + + it('evaluates symbol size', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: '4' }); + await expect(targetAccess.evaluateSymbolSize('var')).resolves.toBe(4); + + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: 'nan' }); + await expect(targetAccess.evaluateSymbolSize('var')).resolves.toBeUndefined(); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('size fail')); + await expect(targetAccess.evaluateSymbolSize('var')).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate size of \'var\' - \'size fail\'' + ); + }); + + it('reads memory and handles errors', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ data: 'AAAA' }); + await expect(targetAccess.evaluateMemory('16', 4, 0)).resolves.toBe('AAAA'); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('custom request failed')); + await expect(targetAccess.evaluateMemory('16', 4, 0)).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to read memory at address \'0x10\' - \'custom request failed\'' + ); + + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('bad read')); + await expect(targetAccess.evaluateMemory('16', 4, 0)).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to read memory at address \'0x10\' - \'bad read\'' + ); + }); + + it('evaluates number of array elements', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: ' 3 ' }); + await expect(targetAccess.evaluateNumberOfArrayElements('arr')).resolves.toBe(3); + + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: 'NaN' }); + await expect(targetAccess.evaluateNumberOfArrayElements('arr')).resolves.toBeUndefined(); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('count fail')); + await expect(targetAccess.evaluateNumberOfArrayElements('arr')).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate number of elements for array \'arr\' - \'count fail\'' + ); + }); + + it('evaluates register values', async () => { + (debugSession.customRequest as jest.Mock).mockResolvedValueOnce({ result: '0x1234' }); + await expect(targetAccess.evaluateRegisterValue('r0')).resolves.toBe('0x1234'); + + const debugSpy = jest.spyOn(logger, 'debug'); + (debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('reg fail')); + await expect(targetAccess.evaluateRegisterValue('r1')).resolves.toBeUndefined(); + expect(debugSpy).toHaveBeenCalledWith( + 'Session \'test-session\': Failed to evaluate register value for \'r1\' - \'reg fail\'' + ); + }); +}); diff --git a/src/views/component-viewer/test/unit/component-viewer-tree-view.test.ts b/src/views/component-viewer/test/unit/component-viewer-tree-view.test.ts new file mode 100644 index 00000000..699b4f18 --- /dev/null +++ b/src/views/component-viewer/test/unit/component-viewer-tree-view.test.ts @@ -0,0 +1,173 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ComponentViewerTreeDataProvider. + */ + +import { ComponentViewerTreeDataProvider } from '../../component-viewer-tree-view'; +import type { ScvdGuiInterface } from '../../model/scvd-gui-interface'; + +const mockFire = jest.fn(); + +jest.mock('vscode', () => { + class EventEmitter { + public fire = mockFire; + public event = jest.fn(); + } + + class TreeItem { + public label: string; + public collapsibleState: number | undefined; + public description: string | undefined; + public tooltip: string | undefined; + public id: string | undefined; + + constructor(label: string) { + this.label = label; + } + } + + return { + EventEmitter, + TreeItem, + TreeItemCollapsibleState: { + Collapsed: 1, + None: 0, + }, + }; +}); + +type TestGui = ScvdGuiInterface & { + nodeId: string; + getGuiName: () => string | undefined; + getGuiValue: () => string | undefined; + getGuiLineInfo: () => string | undefined; + hasGuiChildren: () => boolean; + getGuiChildren: () => ScvdGuiInterface[]; + getGuiEntry: () => { name: string | undefined; value: string | undefined }; + getGuiConditionResult: () => boolean; +}; + +type TestGuiOptions = Partial> & { + getGuiChildren?: () => ScvdGuiInterface[]; +}; + +const makeGui = (options: TestGuiOptions): TestGui => ({ + nodeId: options.nodeId ?? 'node-1', + getGuiName: options.getGuiName ?? (() => 'Node'), + getGuiValue: options.getGuiValue ?? (() => 'Value'), + getGuiLineInfo: options.getGuiLineInfo ?? (() => 'Line 1'), + hasGuiChildren: options.hasGuiChildren ?? (() => false), + getGuiChildren: options.getGuiChildren ?? (() => [] as ScvdGuiInterface[]), + getGuiEntry: options.getGuiEntry ?? (() => ({ name: 'Node', value: 'Value' })), + getGuiConditionResult: options.getGuiConditionResult ?? (() => true), +}); + +describe('ComponentViewerTreeDataProvider', () => { + beforeEach(() => { + mockFire.mockClear(); + }); + + it('builds tree items with fallbacks and collapsible state', () => { + const provider = new ComponentViewerTreeDataProvider(); + const withChildren = makeGui({ + nodeId: 'node-a', + hasGuiChildren: () => true, + }); + const withoutChildren = makeGui({ + nodeId: 'node-b', + getGuiName: () => undefined, + getGuiValue: () => undefined, + getGuiLineInfo: () => undefined, + }); + + const treeItemWithChildren = provider.getTreeItem(withChildren); + expect(treeItemWithChildren.label).toBe('Node'); + expect(treeItemWithChildren.collapsibleState).toBe(1); + expect(treeItemWithChildren.description).toBe('Value'); + expect(treeItemWithChildren.tooltip).toBe('Line 1'); + expect(treeItemWithChildren.id).toBe('node-a'); + + const treeItemWithout = provider.getTreeItem(withoutChildren); + expect(treeItemWithout.label).toBe('UNKNOWN'); + expect(treeItemWithout.collapsibleState).toBe(0); + expect(treeItemWithout.description).toBe(''); + expect(treeItemWithout.tooltip).toBe(''); + expect(treeItemWithout.id).toBe('node-b'); + }); + + it('returns root children when no element is provided', async () => { + const provider = new ComponentViewerTreeDataProvider(); + const root = makeGui({ nodeId: 'root' }); + provider.addGuiOut([root]); + provider.showModelData(); + + expect(provider.getChildren()).resolves.toEqual([root]); + expect(mockFire).toHaveBeenCalledTimes(1); + }); + + it('returns element children in order', async () => { + const provider = new ComponentViewerTreeDataProvider(); + const childA = makeGui({ nodeId: 'child-a' }); + const childB = makeGui({ nodeId: 'child-b' }); + const parent = makeGui({ + nodeId: 'parent', + getGuiChildren: () => [childA, childB], + }); + + expect(provider.getChildren(parent)).resolves.toEqual([childA, childB]); + }); + + it('returns empty children when element has none', async () => { + const provider = new ComponentViewerTreeDataProvider(); + const parent = makeGui({ + nodeId: 'parent-empty', + getGuiChildren: () => undefined as unknown as ScvdGuiInterface[], + }); + + expect(provider.getChildren(parent)).resolves.toEqual([]); + }); + + it('handles empty caches and no gui output', async () => { + const provider = new ComponentViewerTreeDataProvider(); + + provider.activate(); + expect(mockFire).toHaveBeenCalledTimes(1); + expect(provider.getChildren()).resolves.toEqual([]); + + provider.addGuiOut(undefined); + provider.showModelData(); + expect(provider.getChildren()).resolves.toEqual([]); + + provider.resetModelCache(); + expect(provider.getChildren()).resolves.toEqual([]); + }); + + it('deletes models and refreshes', async () => { + const provider = new ComponentViewerTreeDataProvider(); + const root = makeGui({ nodeId: 'root' }); + provider.addGuiOut([root]); + provider.showModelData(); + expect(mockFire).toHaveBeenCalledTimes(1); + + provider.deleteModels(); + expect(mockFire).toHaveBeenCalledTimes(2); + expect(provider.getChildren()).resolves.toEqual([]); + }); +}); diff --git a/src/views/component-viewer/test/unit/gui/scvd-gui-tree.test.ts b/src/views/component-viewer/test/unit/gui/scvd-gui-tree.test.ts new file mode 100644 index 00000000..93dd1a60 --- /dev/null +++ b/src/views/component-viewer/test/unit/gui/scvd-gui-tree.test.ts @@ -0,0 +1,170 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdGuiTree reconciliation and child management. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; + +describe('ScvdGuiTree', () => { + it('reconciles children with epochs, pruning unseen nodes', () => { + const root = new ScvdGuiTree(undefined, 'root'); + const epoch1 = root.beginUpdate(); + const a = root.getOrCreateChild('a', 'a'); + const b = root.getOrCreateChild('b', 'b'); + expect(root.children).toHaveLength(2); + root.finalizeUpdate(epoch1); + expect(root.children).toEqual([a, b]); + + const epoch2 = root.beginUpdate(); + root.getOrCreateChild('a', 'a'); // reuse only 'a' + root.finalizeUpdate(epoch2); + expect(root.children).toEqual([a]); + expect(a.parent).toBe(root); + }); + + it('suffixes duplicate keys', () => { + const root = new ScvdGuiTree(undefined, 'root'); + const epoch = root.beginUpdate(); + root.getOrCreateChild('dup'); + root.getOrCreateChild('dup'); + root.getOrCreateChild('dup'); + root.finalizeUpdate(epoch); + const keys = root.children.map(c => c.key); + expect(keys).toEqual(['dup', 'dup#1', 'dup#2']); + }); + + it('bumps reused children to the end of the list', () => { + const root = new ScvdGuiTree(undefined, 'root'); + const epoch1 = root.beginUpdate(); + const first = root.getOrCreateChild('first'); + const second = root.getOrCreateChild('second'); + root.finalizeUpdate(epoch1); + + const epoch2 = root.beginUpdate(); + root.getOrCreateChild('second'); + const reused = root.getOrCreateChild('first'); + root.finalizeUpdate(epoch2); + + expect(reused).toBe(first); + expect(root.children).toEqual([second, first]); + }); + + it('builds path using ancestors iterator', () => { + const root = new ScvdGuiTree(undefined, 'root'); + const child = root.getOrCreateChild('child'); + const grand = child.getOrCreateChild('grand'); + const path = (grand as unknown as { path: string }).path; + const parts = path.split(' > '); + expect(parts).toHaveLength(3); + expect(parts[0].startsWith('root_')).toBe(true); + expect(parts[2].includes('grand')).toBe(true); + }); + + it('recovers with fallback when child creation throws', () => { + const root = new ScvdGuiTree(undefined, 'root'); + let first = true; + const originalAdd = (root as unknown as { addChild?: (c: ScvdGuiTree) => void }).addChild?.bind(root); + // Throw on the first addChild to trigger fallback path + (root as unknown as { addChild: (c: ScvdGuiTree) => void }).addChild = (c: ScvdGuiTree) => { + if (first) { + first = false; + throw new Error('fail'); + } + originalAdd?.(c); + }; + const epoch = root.beginUpdate(); + jest.spyOn(console, 'error').mockImplementation(() => {}); + const fallback = root.getOrCreateChild('boom'); + root.finalizeUpdate(epoch); + expect(fallback.key).toBe('boom#fallback'); + expect(root.children).toContain(fallback); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('clears and detaches children and indexes correctly', () => { + const root = new ScvdGuiTree(undefined, 'root'); + root.beginUpdate(); + const child = root.getOrCreateChild('child'); + expect(root.hasGuiChildren()).toBe(true); + expect(root.childIndex.has('child')).toBe(true); + child.detach(); + expect(root.children).toHaveLength(0); + expect(root.childIndex.has('child')).toBe(false); + + const epoch = root.beginUpdate(); + const keyless = new ScvdGuiTree(root, 'keyless'); + root.finalizeUpdate(epoch); + expect(root.children).not.toContain(keyless); + + root.beginUpdate(); + root.getOrCreateChild('orphan'); + expect(root.childIndex.size).toBe(1); + root.clear(); + expect(root.hasGuiChildren()).toBe(false); + expect(root.childIndex.size).toBe(0); + + root.detach(); // no parent: should be a no-op branch + expect(root.parent).toBeUndefined(); + + const keylessDetached = new ScvdGuiTree(root, 'keyless-detach'); + expect(keylessDetached.key).toBeUndefined(); + keylessDetached.detach(); + expect(root.children).not.toContain(keylessDetached); + }); + + it('exposes gui getters and setters', () => { + const node = new ScvdGuiTree(undefined, 'node'); + node.setGuiValue('Value'); + node.isPrint = true; + (node as unknown as { name?: string }).name = 'Internal'; + expect(node.getGuiName()).toBe('Internal'); + node.setGuiName('Name'); + expect(node.getGuiValue()).toBe('Value'); + expect(node.getGuiEntry()).toEqual({ name: 'Name', value: 'Value' }); + expect(node.getGuiChildren()).toEqual([]); + expect(node.getGuiConditionResult()).toBe(true); + expect(node.getGuiLineInfo()).toBeUndefined(); + expect(node.isPrint).toBe(true); + expect(node.hasGuiChildren()).toBe(false); + }); + + it('resets duplicate counters per epoch and allows external epoch set', () => { + ScvdGuiTree.epoch = 10; + const root = new ScvdGuiTree(undefined, 'root'); + + const epoch1 = root.beginUpdate(); + expect(epoch1).toBe(11); + const firstA = root.getOrCreateChild('a'); + const secondA = root.getOrCreateChild('a'); + root.finalizeUpdate(epoch1); + expect(firstA.key).toBe('a'); + expect(secondA.key).toBe('a#1'); + + const epoch2 = root.beginUpdate(); + expect(epoch2).toBe(12); + const reused = root.getOrCreateChild('a'); + expect(reused).toBe(firstA); + root.getOrCreateChild('b'); + root.finalizeUpdate(epoch2); + const keys = root.children.map(c => c.key); + expect(keys).toEqual(['a', 'b']); + expect(root.keyCursor.size).toBeGreaterThanOrEqual(0); + }); +}); diff --git a/src/views/component-viewer/test/unit/helpers/statement-engine-helpers.ts b/src/views/component-viewer/test/unit/helpers/statement-engine-helpers.ts new file mode 100644 index 00000000..b6b58822 --- /dev/null +++ b/src/views/component-viewer/test/unit/helpers/statement-engine-helpers.ts @@ -0,0 +1,97 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Test helpers for statement-engine. + */ + +import { EvalContext } from '../../../parser-evaluator/evaluator'; +import type { ExecutionContext } from '../../../scvd-eval-context'; +import { MemoryHost } from '../../../data-host/memory-host'; +import { RegisterHost } from '../../../data-host/register-host'; +import type { ScvdDebugTarget } from '../../../scvd-debug-target'; +import { ScvdNode } from '../../../model/scvd-node'; + +export class TestNode extends ScvdNode { + private _conditionResult: boolean; + private _guiName: string | undefined; + private _guiValue: string | undefined; + + constructor( + parent: ScvdNode | undefined, + opts?: { + conditionResult?: boolean; + guiName?: string; + guiValue?: string; + } + ) { + super(parent); + this._conditionResult = opts?.conditionResult ?? true; + this._guiName = opts?.guiName; + this._guiValue = opts?.guiValue; + } + + public override async getConditionResult(): Promise { + return this._conditionResult; + } + + public override async getGuiName(): Promise { + return this._guiName; + } + + public override async getGuiValue(): Promise { + return this._guiValue; + } + + public set conditionResult(value: boolean) { + this._conditionResult = value; + } + + public set guiName(value: string | undefined) { + this._guiName = value; + } + + public set guiValue(value: string | undefined) { + this._guiValue = value; + } +} + +export function createExecutionContext( + base: ScvdNode, + debugTargetOverrides?: Partial +): ExecutionContext { + const memoryHost = new MemoryHost(); + const registerHost = new RegisterHost(); + const evalContext = new EvalContext({ + data: {} as never, + container: base, + }); + const debugTargetDefaults: Partial = { + findSymbolAddress: async () => undefined, + getNumArrayElements: async () => undefined, + readMemory: async () => undefined, + }; + const debugTarget = { ...debugTargetDefaults, ...debugTargetOverrides } as ScvdDebugTarget; + + return { + memoryHost, + registerHost, + evalContext, + debugTarget, + }; +} diff --git a/src/views/component-viewer/test/unit/memory-host/memory-host.test.ts b/src/views/component-viewer/test/unit/memory-host/memory-host.test.ts new file mode 100644 index 00000000..6b1ca35e --- /dev/null +++ b/src/views/component-viewer/test/unit/memory-host/memory-host.test.ts @@ -0,0 +1,316 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for MemoryHost. + */ + +import { MemoryContainer, MemoryHost } from '../../../data-host/memory-host'; +import { RefContainer } from '../../../parser-evaluator/model-host'; +import { ScvdNode } from '../../../model/scvd-node'; + +class NamedStubBase extends ScvdNode { + constructor(name: string) { + super(undefined); + this.name = name; + } +} + +const makeRef = ( + name: string, + widthBytes: number, + offsetBytes = 0, + valueType?: RefContainer['valueType'], + withAnchor = true +): RefContainer => { + const ref = new NamedStubBase(name); + return { + base: ref, + anchor: withAnchor ? ref : undefined, + current: ref, + offsetBytes, + widthBytes, + valueType: valueType ?? undefined, + }; +}; + +describe('MemoryHost', () => { + it('roundtrips numeric values', async () => { + const host = new MemoryHost(); + const ref = makeRef('num', 4); + + await host.writeValue(ref, 0x12345678); + + const out = await host.readValue(ref); + expect(out).toBe(0x12345678 >>> 0); + }); + + it('reads and writes via MemoryContainer', () => { + const container = new MemoryContainer('blob'); + container.write(0, new Uint8Array([1, 2, 3, 4])); + + const out = container.read(1, 2); + expect(Array.from(out)).toEqual([2, 3]); + + container.clear(); + expect(container.byteLength).toBe(0); + }); + + it('zero-fills when virtualSize exceeds payload', () => { + const container = new MemoryContainer('pad'); + container.write(0, new Uint8Array([9, 8]), 4); + expect(Array.from(container.read(0, 4))).toEqual([9, 8, 0, 0]); + }); + + it('logs when container window is unavailable', () => { + const container = new MemoryContainer('bad'); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + (container as unknown as { ensure: () => void }).ensure = () => {}; + + expect(Array.from(container.read(0, 2))).toEqual([0, 0]); + container.write(0, new Uint8Array([1])); + + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + it('handles readValue float types and raw byte output', async () => { + const host = new MemoryHost(); + const f32 = new DataView(new ArrayBuffer(4)); + f32.setFloat32(0, 1.25, true); + host.setVariable('f32', 4, new Uint8Array(f32.buffer), 0); + + const f64 = new DataView(new ArrayBuffer(8)); + f64.setFloat64(0, 2.5, true); + host.setVariable('f64', 8, new Uint8Array(f64.buffer), 0); + host.setVariable('f16', 2, new Uint8Array([0x00, 0x80]), 0); + + const f32Ref = makeRef('f32', 4, 0, { kind: 'float' }); + const f64Ref = makeRef('f64', 8, 0, { kind: 'float' }); + const f16Ref = makeRef('f16', 2, 0, { kind: 'float' }); + + expect(await host.readValue(f32Ref)).toBeCloseTo(1.25); + expect(await host.readValue(f64Ref)).toBeCloseTo(2.5); + expect(await host.readValue(f16Ref)).toBe(0x8000); + + const bigRef = makeRef('blob', 10); + const bytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + await host.writeValue(bigRef, bytes); + const out = await host.readValue(bigRef); + expect(out).toEqual(bytes); + expect(out).not.toBe(bytes); + + const raw = await host.readRaw(bigRef, 4); + expect(raw).toEqual(new Uint8Array([1, 2, 3, 4])); + }); + + it('handles bigint reads and non-little endianness branch', async () => { + const host = new MemoryHost(); + const ref = makeRef('big', 8); + await host.writeValue(ref, 0x0102030405060708n); + const out = await host.readValue(ref); + expect(out).toBe(0x0102030405060708n); + + (host as unknown as { endianness: string }).endianness = 'big'; + expect(await host.readValue(ref)).toBe(0x0102030405060708n); + }); + + it('returns undefined for invalid readValue/readRaw inputs', async () => { + const host = new MemoryHost(); + const missing = makeRef('missing', 4, 0, undefined, false); + + expect(await host.readValue(missing)).toBeUndefined(); + expect(await host.readValue(makeRef('bad', 0))).toBeUndefined(); + const undefWidth: RefContainer = { + ...makeRef('undef', 1), + widthBytes: undefined, + }; + expect(await host.readValue(undefWidth)).toBeUndefined(); + expect(await host.readRaw(missing, 4)).toBeUndefined(); + expect(await host.readRaw(makeRef('bad', 4), 0)).toBeUndefined(); + + await host.writeValue(makeRef('bad', 0), 1); + }); + + it('writes values with coercion and validates virtualSize', async () => { + const host = new MemoryHost(); + const ref = makeRef('bytes', 4); + await host.writeValue(ref, new Uint8Array([1, 2])); + expect(await host.readRaw(ref, 4)).toEqual(new Uint8Array([1, 2, 0, 0])); + + await host.writeValue(ref, true); + expect(await host.readValue(ref)).toBe(1); + await host.writeValue(ref, false); + expect(await host.readValue(ref)).toBe(0); + + const bigRef = makeRef('bigint', 8); + await host.writeValue(bigRef, 0x0102n); + expect(await host.readValue(bigRef)).toBe(0x0102n); + + await host.writeValue(ref, new Uint8Array([9, 8, 7, 6])); + expect(await host.readRaw(ref, 4)).toEqual(new Uint8Array([9, 8, 7, 6])); + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + await host.writeValue(ref, 'bad' as unknown as number); + await host.writeValue(ref, 5, 2); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + it('handles setVariable metadata and error cases', () => { + const host = new MemoryHost(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + host.setVariable('badOffset', 1, 1, Number.NaN); + host.setVariable('neg', 1, 1, -2); + host.setVariable('badType', 1, 'oops' as unknown as number, 0); + host.setVariable('badSize', 1, 1, 0, undefined, 0); + + host.setVariable('arr', 2, 1, -1, 0x1000, 4); + host.setVariable('arr', 2, 2n, -1, -5, 6); + host.setVariable('buf', 4, new Uint8Array([1, 2, 3, 4]), 0); + host.setVariable('buf', 4, new Uint8Array([5, 6]), 4); + expect(host.getArrayElementCount('arr')).toBe(2); + expect(host.getArrayTargetBases('arr')).toEqual([0x1000, 0]); + + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + it('returns variables with inferred sizes and validates ranges', async () => { + const host = new MemoryHost(); + host.setVariable('item', 2, 0x1234, -1, undefined, 2); + host.setVariable('item', 2, 0x5678, -1, undefined, 2); + + expect(host.getVariable('item')).toBe(0x1234); + expect(host.getVariable('item', undefined, 0)).toBe(0x1234); + expect(host.getVariable('item', undefined, 2)).toBe(0x5678); + + host.setVariable('wide', 4, 0x11223344, -1, undefined, 4); + host.setVariable('wide', 4, 0x55667788, -1, undefined, 4); + expect(host.getVariable('wide', undefined, 1)).toBeUndefined(); + expect(host.getVariable('item', 0, 0)).toBeUndefined(); + expect(host.getVariable('item', 2, -1)).toBeUndefined(); + expect(host.getVariable('item', 4, 2)).toBeUndefined(); + expect(host.getVariable('item', 2, 99)).toBeUndefined(); + expect(host.getVariable('missing')).toBeUndefined(); + + const rawRef = makeRef('raw', 2); + await host.writeValue(rawRef, new Uint8Array([0x34, 0x12])); + expect(host.getVariable('raw', undefined, 0)).toBe(0x1234); + }); + + it('supports invalidate/clear operations', () => { + const host = new MemoryHost(); + host.setVariable('clearme', 2, 0x1111, 0); + expect(host.clearVariable('clearme')).toBe(true); + host.invalidate('clearme'); + host.invalidate(); + expect(host.clearVariable('missing')).toBe(false); + + host.setVariable('temp', 2, 0x2222, 0); + host.clear(); + expect(host.getArrayElementCount('temp')).toBe(1); + }); + + it('validates element base accessors', () => { + const host = new MemoryHost(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(host.getElementTargetBase('none', 0)).toBeUndefined(); + expect(host.getArrayTargetBases('none')).toEqual([]); + host.setVariable('bases', 1, 1, -1, 0x10); + expect(host.getElementTargetBase('bases', 2)).toBeUndefined(); + + host.setElementTargetBase('none', 0, 0x20); + host.setElementTargetBase('bases', 2, 0x20); + host.setElementTargetBase('bases', 0, -1); + expect(host.getElementTargetBase('bases', 0)).toBe(0x10); + host.setElementTargetBase('bases', 0, 0x20); + expect(host.getElementTargetBase('bases', 0)).toBe(0x20); + + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + it('computes array length from bytes when metadata is sparse', () => { + const host = new MemoryHost(); + expect(host.getArrayLengthFromBytes('missing')).toBe(1); + + host.setVariable('len', 2, 1, -1); + host.setVariable('len', 2, 2, -1); + expect(host.getArrayLengthFromBytes('len')).toBe(2); + + const meta = { offsets: [], sizes: [], bases: [], elementSize: 4 }; + (host as unknown as { elementMeta: Map }).elementMeta.set('stride', meta); + const container = (host as unknown as { getContainer: (name: string) => MemoryContainer }).getContainer('stride'); + container.write(0, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9])); + expect(host.getArrayLengthFromBytes('stride')).toBe(2); + + (host as unknown as { elementMeta: Map }).elementMeta.set('nostride', { + offsets: [], + sizes: [], + bases: [], + elementSize: 0, + }); + expect(host.getArrayLengthFromBytes('nostride')).toBe(1); + }); + + it('exercises nullish defaults for offsets and sizes', async () => { + const host = new MemoryHost(); + const ref = makeRef('defaults', 2); + const customRef: RefContainer = { + ...ref, + offsetBytes: undefined, + widthBytes: undefined, + }; + await host.writeValue(customRef, 1); + + const readRef: RefContainer = { + ...ref, + offsetBytes: undefined, + }; + await host.writeValue(readRef, new Uint8Array([0xAA, 0xBB])); + expect(await host.readRaw(readRef, 2)).toEqual(new Uint8Array([0xAA, 0xBB])); + + const readValueRef: RefContainer = { + ...readRef, + widthBytes: 2, + }; + expect(await host.readValue(readValueRef)).toBe(0xBBAA); + }); + + it('covers nullish byteLength fallbacks', () => { + const host = new MemoryHost(); + const container = (host as unknown as { getContainer: (name: string) => MemoryContainer }).getContainer('nullish'); + Object.defineProperty(container, 'byteLength', { get: () => undefined }); + + host.setVariable('nullish', 1, 1, -1); + expect(host.getVariable('nullish', undefined, 0)).toBeUndefined(); + + (host as unknown as { elementMeta: Map }).elementMeta.set('nullishMeta', { + offsets: [], + sizes: [], + bases: [], + elementSize: 4, + }); + const metaContainer = (host as unknown as { getContainer: (name: string) => MemoryContainer }).getContainer('nullishMeta'); + Object.defineProperty(metaContainer, 'byteLength', { get: () => undefined }); + expect(host.getArrayLengthFromBytes('nullishMeta')).toBe(1); + }); +}); diff --git a/src/views/component-viewer/test/unit/memory-host/register-host.test.ts b/src/views/component-viewer/test/unit/memory-host/register-host.test.ts new file mode 100644 index 00000000..030f631d --- /dev/null +++ b/src/views/component-viewer/test/unit/memory-host/register-host.test.ts @@ -0,0 +1,73 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for RegisterHost. + */ + +import { RegisterHost } from '../../../data-host/register-host'; + +describe('RegisterHost', () => { + it('normalizes register names and stores values', () => { + const host = new RegisterHost(); + host.write(' r0 ', 0x1234); + + expect(host.read('R0')).toBe(0x1234); + expect(host.read('r0')).toBe(0x1234); + }); + + it('handles empty register names with errors', () => { + const host = new RegisterHost(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(host.read('')).toBeUndefined(); + expect(host.write('', 1)).toBeUndefined(); + + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + it('truncates values to uint32', () => { + const host = new RegisterHost(); + host.write('r1', 0x1_0000_0001); + host.write('r2', 0x1_0000_0001n); + + expect(host.read('r1')).toBe(1); + expect(host.read('r2')).toBe(1n); + }); + + it('returns the original value from write', () => { + const host = new RegisterHost(); + + expect(host.write('r5', 5)).toBe(5); + expect(host.write('r6', 6n)).toBe(6n); + }); + + it('invalidates and clears cached registers', () => { + const host = new RegisterHost(); + host.write('r3', 3); + host.write('r4', 4); + + host.invalidate('r3'); + expect(host.read('r3')).toBeUndefined(); + expect(host.read('r4')).toBe(4); + + host.clear(); + expect(host.read('r4')).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/memory-host/validating-cache.test.ts b/src/views/component-viewer/test/unit/memory-host/validating-cache.test.ts new file mode 100644 index 00000000..f40baf24 --- /dev/null +++ b/src/views/component-viewer/test/unit/memory-host/validating-cache.test.ts @@ -0,0 +1,81 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ValidatingCache. + */ + +import { ValidatingCache } from '../../../data-host/validating-cache'; + +describe('ValidatingCache', () => { + it('normalizes keys on get/set and respects valid flags', () => { + const normalize = jest.fn((key: string) => key.trim().toLowerCase()); + const cache = new ValidatingCache(normalize); + + cache.set(' A ', 1); + cache.set(' B ', 2, false); + + expect(cache.get('a')).toBe(1); + expect(cache.get('b')).toBeUndefined(); + expect(normalize).toHaveBeenCalled(); + }); + + it('returns undefined for empty keys', () => { + const cache = new ValidatingCache(); + + cache.set('', 1); + expect(cache.get('')).toBeUndefined(); + expect(cache.delete('')).toBe(false); + }); + + it('ensures a value with a factory and reuses existing entries', () => { + const cache = new ValidatingCache(); + const factory = jest.fn(() => 'value'); + + expect(cache.ensure('key', factory)).toBe('value'); + expect(cache.ensure('key', () => 'new')).toBe('value'); + expect(factory).toHaveBeenCalledTimes(1); + }); + + it('invalidates entries and supports bulk invalidation', () => { + const cache = new ValidatingCache(); + cache.set('one', 1); + cache.set('two', 2); + + cache.invalidate(''); + cache.invalidate('missing'); + cache.invalidate('one'); + expect(cache.get('one')).toBeUndefined(); + expect(cache.get('two')).toBe(2); + + cache.invalidateAll(); + expect(cache.get('two')).toBeUndefined(); + }); + + it('clears and deletes keys', () => { + const cache = new ValidatingCache(); + cache.set('one', 1); + cache.set('two', 2); + + expect(cache.delete('two')).toBe(true); + expect(cache.delete('missing')).toBe(false); + + cache.clear(); + expect(cache.get('one')).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/number-type.test.ts b/src/views/component-viewer/test/unit/model/number-type.test.ts new file mode 100644 index 00000000..4bf0dfca --- /dev/null +++ b/src/views/component-viewer/test/unit/model/number-type.test.ts @@ -0,0 +1,234 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for NumberType. + */ + +import { NumFormat, NumberType } from '../../../model/number-type'; + +describe('NumberType', () => { + it('constructs from numbers and respects formats', () => { + const num = new NumberType(42); + expect(num.format).toBe(NumFormat.decimal); + expect(num.displayFormat).toBe(NumFormat.decimal); + expect(num.value).toBe(42); + + const hex = new NumberType(255, NumFormat.hexadecimal, 8); + expect(hex.format).toBe(NumFormat.hexadecimal); + expect(hex.displayFormat).toBe(NumFormat.hexadecimal); + expect(hex.numOfDisplayBits).toBe(8); + }); + + it('copies from another NumberType and clamps digit setters', () => { + const base = new NumberType(7, NumFormat.decimal); + base.numOfDigits = 0; + base.numOfDisplayBits = 0; + expect(base.numOfDigits).toBe(1); + expect(base.numOfDisplayBits).toBe(1); + + const copy = new NumberType(base); + copy.numOfDigits = -2; + copy.numOfDisplayBits = -3; + expect(copy.numOfDigits).toBe(1); + expect(copy.numOfDisplayBits).toBe(1); + expect(copy.value).toBe(7); + }); + + it('parses string inputs and booleans', () => { + const hex = new NumberType('0x1F'); + expect(hex.format).toBe(NumFormat.hexadecimal); + expect(hex.getText()).toBe('0x1F'); + + const bin = new NumberType('0b1010'); + expect(bin.format).toBe(NumFormat.binary); + expect(bin.getText()).toBe('0b1010'); + + const oct = new NumberType('075'); + expect(oct.format).toBe(NumFormat.octal); + expect(oct.getText()).toBe('075'); + + const dec = new NumberType('123'); + expect(dec.format).toBe(NumFormat.decimal); + expect(dec.getText()).toBe('123'); + + const t = new NumberType('true'); + const f = new NumberType('false'); + expect(t.format).toBe(NumFormat.boolean); + expect(t.getText()).toBe('true'); + expect(f.format).toBe(NumFormat.boolean); + expect(f.getText()).toBe('false'); + }); + + it('handles invalid and empty strings', () => { + const invalid = new NumberType('xyz'); + expect(invalid.isValid()).toBe(false); + expect(invalid.getText()).toBe('0'); + + const empty = new NumberType(''); + expect(empty.isValid()).toBe(false); + expect(empty.getText()).toBe('0'); + + const single = new NumberType('7'); + expect(single.format).toBe(NumFormat.decimal); + + const neg = new NumberType('-1'); + expect(neg.value).toBe(-1); + + const hexEmpty = new NumberType('0x'); + expect(hexEmpty.numOfDigits).toBe(1); + }); + + it('formats negative values and boolean defaults', () => { + const neg = new NumberType(-15, NumFormat.hexadecimal); + expect(neg.getValStrByFormat(NumFormat.hexadecimal, 2)).toBe('-0x0F'); + + const bool = new NumberType(2, NumFormat.boolean); + expect(bool.getValStrByFormat(NumFormat.boolean, 1)).toBe('true'); + }); + + it('supports display formatting rules', () => { + const display = new NumberType(0xA, NumFormat.hexadecimal, 5); + expect(display.getDisplayText()).toBe('0x0A'); + + display.displayFormat = NumFormat.decimal; + expect(display.getDisplayText()).toBe('10'); + + display.displayFormat = NumFormat.octal; + display.numOfDisplayBits = 4; + expect(display.getDisplayText()).toBe('012'); + + display.displayFormat = NumFormat.undefined; + expect(display.getDisplayText()).toBe('10'); + + const small = new NumberType(1, NumFormat.hexadecimal, 0); + expect(small.getDisplayText()).toBe('0x1'); + }); + + it('supports min/max bounds and accessors', () => { + const bounded = new NumberType(10); + bounded.setMin(3); + bounded.setMax(8); + expect(bounded.value).toBe(8); + + bounded.value = 1; + expect(bounded.value).toBe(3); + + bounded.setMinMax(2, undefined); + bounded.setMinMax(undefined, 12); + expect(bounded.min).toBe(2); + expect(bounded.max).toBe(12); + expect(bounded.getMinMax()).toEqual({ min: 2, max: 12 }); + }); + + it('exposes format prefix and text helpers', () => { + const num = new NumberType(0); + expect(num.getFormatPrefix(NumFormat.decimal)).toBe(''); + expect(num.getFormatPrefix(NumFormat.hexadecimal)).toBe('0x'); + expect(num.getFormatPrefix(NumFormat.octal)).toBe('0'); + expect(num.getFormatPrefix(NumFormat.binary)).toBe('0b'); + expect(num.getFormatPrefix(NumFormat.undefined)).toBe(''); + + expect(num.getFormatText(NumFormat.decimal)).toBe('decimal'); + expect(num.getFormatText(NumFormat.hexadecimal)).toBe('hexadecimal'); + expect(num.getFormatText(NumFormat.octal)).toBe('octal'); + expect(num.getFormatText(NumFormat.binary)).toBe('binary'); + expect(num.getFormatText(NumFormat.boolean)).toBe('boolean'); + expect(num.getFormatText(NumFormat.undefined)).toBe('undefined'); + }); + + it('updates values and formats through setters', () => { + const num = new NumberType(); + num.value = 3; + expect(num.format).toBe(NumFormat.decimal); + expect(num.displayFormat).toBe(NumFormat.decimal); + + const other = new NumberType(5, NumFormat.octal); + num.value = other; + expect(num.format).toBe(NumFormat.octal); + + num.value = '0b11'; + expect(num.format).toBe(NumFormat.binary); + + num.format = NumFormat.hexadecimal; + expect(num.format).toBe(NumFormat.hexadecimal); + }); + + it('covers string setter branch directly', () => { + const num = new NumberType(); + const setter = Object.getOwnPropertyDescriptor(NumberType.prototype, 'value')?.set; + expect(typeof setter).toBe('function'); + setter?.call(num, '0b1'); + expect(num.format).toBe(NumFormat.binary); + }); + + it('covers non-string setter fallthrough', () => { + const num = new NumberType(11); + const setter = Object.getOwnPropertyDescriptor(NumberType.prototype, 'value')?.set; + setter?.call(num, true as unknown as number); + expect(num.value).toBe(11); + }); + + it('forces fallback when formatted text is empty', () => { + const originalToString = Number.prototype.toString; + Number.prototype.toString = () => ''; + try { + const num = new NumberType(7, NumFormat.decimal); + expect(num.getValStrByFormat(NumFormat.decimal, 1)).toBe(''); + } finally { + Number.prototype.toString = originalToString; + } + }); + + it('pads digits when the requested width is below one', () => { + const num = new NumberType(5, NumFormat.hexadecimal); + expect(num.getValStrByFormat(NumFormat.hexadecimal, 0)).toBe('0x5'); + }); + + it('covers setter branches and protected toNumber parsing', () => { + const num = new NumberType(); + num.numOfDigits = 0; + num.numOfDigits = 2; + + num.value = '0'; + expect(num.format).toBe(NumFormat.decimal); + + const parsed = (num as unknown as { toNumber: (v: string) => { displayFormat: NumFormat } }).toNumber('-2'); + expect(parsed.displayFormat).toBe(NumFormat.decimal); + + expect(num.getFormatText(NumFormat.decimal)).toBe('decimal'); + }); + + it('covers displayFormat branch when enum value is NaN', () => { + const enumRef = NumFormat as unknown as { undefined: number }; + const original = enumRef.undefined; + enumRef.undefined = Number.NaN; + try { + const num = new NumberType(); + const parsed = (num as unknown as { toNumber: (v: string) => { displayFormat: number } }).toNumber('1'); + expect(Number.isNaN(parsed.displayFormat)).toBe(true); + } finally { + enumRef.undefined = original; + } + }); + + it('covers default branch in getFormatText', () => { + const num = new NumberType(); + expect(num.getFormatText(99 as NumFormat)).toBe('undefined'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-base.test.ts b/src/views/component-viewer/test/unit/model/scvd-base.test.ts new file mode 100644 index 00000000..106f18ef --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-base.test.ts @@ -0,0 +1,203 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdBase. + */ + +import { ScvdBase } from '../../../model/scvd-base'; + +class TestBase extends ScvdBase { + public override get classname(): string { + return 'TestBase'; + } + + public addToSymbolContext(name: string | undefined, symbol: ScvdBase): void { + super.addToSymbolContext(name, symbol); + } +} + +class SymbolBase extends ScvdBase { + public override get classname(): string { + return 'SymbolBase'; + } + + public override getSymbol(_name: string): ScvdBase | undefined { + return this; + } +} + +class UndefinedTagBase extends ScvdBase { + public override get classname(): string { + return 'UndefinedTagBase'; + } + + public override get tag(): string | undefined { + return undefined; + } +} + +describe('ScvdBase', () => { + beforeEach(() => { + ScvdBase.resetIds(); + }); + + it('tracks parent/child relationships and ids', () => { + const parent = new TestBase(undefined); + const child = new TestBase(parent); + + expect(parent.children).toEqual([child]); + expect(child.parent).toBe(parent); + expect(child.nodeId).toMatch(/^TestBase_\d+$/); + expect(child.classname).toBe('TestBase'); + + expect(parent.castToDerived(TestBase)).toBe(parent); + expect(parent.castToDerived(SymbolBase)).toBeUndefined(); + }); + + it('handles metadata and flags', () => { + const base = new TestBase(undefined); + base.tag = undefined; + base.lineNo = '10'; + base.lineNo = undefined; + base.name = 'Name'; + base.info = 'Info'; + base.isModified = true; + base.valid = true; + base.mustRead = false; + + expect(base.tag).toBe('Internal Object'); + expect(base.lineNo).toBe('10'); + expect(base.name).toBe('Name'); + expect(base.info).toBe('Info'); + expect(base.isModified).toBe(true); + expect(base.valid).toBe(true); + expect(base.mustRead).toBe(false); + expect(base.lineNo).toBe('10'); + }); + + it('supports traversal helpers', () => { + const parent = new TestBase(undefined); + const a = new TestBase(parent); + const b = new TestBase(parent); + + expect(parent.map(child => child)).toEqual([a, b]); + const seen: ScvdBase[] = []; + parent.forEach(child => seen.push(child)); + expect(seen).toEqual([a, b]); + expect(parent.filter(child => child === b)).toEqual([b]); + }); + + it('delegates symbol context to parents', () => { + const parent = new TestBase(undefined); + const child = new TestBase(parent); + + const symbol = new TestBase(undefined); + const spy = jest.spyOn(parent, 'addToSymbolContext'); + + child.addToSymbolContext('name', symbol); + expect(spy).toHaveBeenCalledWith('name', symbol); + }); + + it('delegates symbol lookups to parents', () => { + const parent = new SymbolBase(undefined); + const child = new TestBase(parent); + + expect(child.getSymbol('anything')).toBe(parent); + }); + + it('builds line info and display labels', () => { + const base = new TestBase(undefined); + base.tag = 'Tag'; + base.lineNo = '5'; + base.name = 'Name'; + + expect(base.getLineInfoStr()).toBe('[Line: 5 Tag: Tag ]'); + expect(base.getLineNoStr()).toBe('5'); + expect(base.getDisplayLabel()).toBe('Name'); + + base.name = undefined; + base.info = 'Info'; + expect(base.getDisplayLabel()).toBe('Info'); + + base.info = undefined; + expect(base.getDisplayLabel()).toBe('Tag (line 5)'); + + base.tag = ''; + expect(base.getDisplayLabel()).toBe('TestBase (line 5)'); + expect(base.getLineInfoStr()).toBe('[Line: 5 Tag: ]'); + }); + + it('handles line sorting with NaN values', () => { + const a = new TestBase(undefined); + const b = new TestBase(undefined); + a.lineNo = 'NaN'; + b.lineNo = '2'; + + expect((a as unknown as { sortByLine: (x: ScvdBase, y: ScvdBase) => number }).sortByLine(a, b)).toBeLessThan(0); + }); + + it('recurses line info when child line number is missing', () => { + const parent = new TestBase(undefined); + parent.lineNo = '12'; + const child = new TestBase(parent); + child.tag = 'Child'; + + expect(child.getLineNoStr()).toBe('12'); + expect(child.getLineInfoStr()).toBe('[Line: 12 Tag: Child ]'); + }); + + it('returns empty line info when no line number is available', () => { + const base = new TestBase(undefined); + base.tag = 'Tag'; + + expect(base.getLineNoStr()).toBe(''); + expect(base.getLineInfoStr()).toBe('[Tag: Tag ]'); + }); + + it('omits tag output when tag getter returns undefined', () => { + const base = new UndefinedTagBase(undefined); + expect(base.getLineInfoStr()).toBe('[]'); + }); + + it('handles NaN on both sides when sorting', () => { + const a = new TestBase(undefined); + const b = new TestBase(undefined); + a.lineNo = 'NaN'; + b.lineNo = 'NaN'; + + expect((a as unknown as { sortByLine: (x: ScvdBase, y: ScvdBase) => number }).sortByLine(a, b)).toBe(0); + }); + + it('sorts numeric line numbers without NaN fallback', () => { + const a = new TestBase(undefined); + const b = new TestBase(undefined); + a.lineNo = '1'; + b.lineNo = '2'; + + expect((a as unknown as { sortByLine: (x: ScvdBase, y: ScvdBase) => number }).sortByLine(a, b)).toBeLessThan(0); + }); + + it('returns default behaviors', () => { + const base = new TestBase(undefined); + expect(base.hasChildren()).toBe(false); + expect(base.configure()).toBe(true); + expect(base.validate(true)).toBe(true); + expect(base.reset()).toBe(true); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-break.test.ts b/src/views/component-viewer/test/unit/model/scvd-break.test.ts new file mode 100644 index 00000000..9c97e2e0 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-break.test.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdBreak and ScvdBreaks. + */ + +import { ScvdBreak, ScvdBreaks } from '../../../model/scvd-break'; +import { Json } from '../../../model/scvd-base'; +import { ScvdCondition } from '../../../model/scvd-condition'; + +describe('ScvdBreak', () => { + it('returns false when XML is undefined', () => { + const breaks = new ScvdBreaks(undefined); + expect(breaks.readXml(undefined as unknown as Json)).toBe(false); + expect(breaks.tag).toBe('XML undefined'); + }); + + it('reads break entries and appends to the list', () => { + const breaks = new ScvdBreaks(undefined); + const breakSpy = jest.spyOn(ScvdBreak.prototype, 'readXml').mockReturnValue(true); + + expect(breaks.readXml({ break: [{}, {}] })).toBe(true); + expect(breaks.breaks).toHaveLength(2); + expect(breaks.addBreak()).toBeInstanceOf(ScvdBreak); + expect(breaks.breaks).toHaveLength(3); + + breakSpy.mockRestore(); + }); + + it('reads a break entry when XML is present', () => { + const item = new ScvdBreak(undefined); + expect(item.readXml({})).toBe(true); + }); + + it('returns false when break XML is undefined', () => { + const item = new ScvdBreak(undefined); + expect(item.readXml(undefined as unknown as Json)).toBe(false); + expect(item.tag).toBe('XML undefined'); + }); + + it('creates a condition and evaluates it when present', async () => { + const item = new ScvdBreak(undefined); + const condSpy = jest.spyOn(ScvdCondition.prototype, 'getResult').mockResolvedValue(false); + + item.cond = '1'; + expect(item.cond).toBeInstanceOf(ScvdCondition); + await expect(item.getConditionResult()).resolves.toBe(false); + + condSpy.mockRestore(); + }); + + it('ignores undefined condition assignments', () => { + const item = new ScvdBreak(undefined); + item.cond = undefined; + expect(item.cond).toBeUndefined(); + }); + + it('falls back to default condition when no cond is set', async () => { + const item = new ScvdBreak(undefined); + await expect(item.getConditionResult()).resolves.toBe(true); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-calc.test.ts b/src/views/component-viewer/test/unit/model/scvd-calc.test.ts new file mode 100644 index 00000000..b4c93f5a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-calc.test.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdCalc. + */ + +import { ScvdCalc } from '../../../model/scvd-calc'; +import { Json } from '../../../model/scvd-base'; +import { ScvdCondition } from '../../../model/scvd-condition'; + +describe('ScvdCalc', () => { + it('returns false when XML is undefined', () => { + const calc = new ScvdCalc(undefined); + expect(calc.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads conditions and expressions from text body', () => { + const calc = new ScvdCalc(undefined); + const xml = { + cond: '1', + '#text': 'A;B\nC' + }; + + expect(calc.readXml(xml)).toBe(true); + expect(calc.cond).toBeInstanceOf(ScvdCondition); + expect(calc.expression).toHaveLength(3); + }); + + it('reuses the same condition instance for updates', () => { + const calc = new ScvdCalc(undefined); + calc.cond = '1'; + const original = calc.cond; + calc.cond = '2'; + + expect(calc.cond).toBe(original); + }); + + it('uses condition when present', async () => { + const calc = new ScvdCalc(undefined); + calc.cond = '1'; + + const resultSpy = jest.spyOn(ScvdCondition.prototype, 'getResult').mockResolvedValue(false); + await expect(calc.getConditionResult()).resolves.toBe(false); + + resultSpy.mockRestore(); + }); + + it('returns default condition when none is set', async () => { + const calc = new ScvdCalc(undefined); + await expect(calc.getConditionResult()).resolves.toBe(true); + }); + + it('returns undefined when adding an empty expression', () => { + const calc = new ScvdCalc(undefined); + const addExpression = (calc as unknown as { addExpression: (value: string | undefined) => unknown }).addExpression; + expect(addExpression(undefined)).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-component-identifier.test.ts b/src/views/component-viewer/test/unit/model/scvd-component-identifier.test.ts new file mode 100644 index 00000000..d1df567f --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-component-identifier.test.ts @@ -0,0 +1,47 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdComponentIdentifier. + */ + +import { ScvdComponentIdentifier } from '../../../model/scvd-component-identifier'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdComponentIdentifier', () => { + it('returns false when XML is undefined', () => { + const identifier = new ScvdComponentIdentifier(undefined); + expect(identifier.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads version and shortname fields from XML', () => { + const identifier = new ScvdComponentIdentifier(undefined); + const xml = { version: '1.2.3', shortname: 'Comp' }; + expect(identifier.readXml(xml)).toBe(true); + expect(identifier.version).toBe('1.2.3'); + expect(identifier.shortName).toBe('Comp'); + }); + + it('tracks version and short name values', () => { + const identifier = new ScvdComponentIdentifier(undefined); + identifier.version = '2.0'; + identifier.shortName = 'New'; + expect(identifier.version).toBe('2.0'); + expect(identifier.shortName).toBe('New'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-component-number.test.ts b/src/views/component-viewer/test/unit/model/scvd-component-number.test.ts new file mode 100644 index 00000000..3de04a73 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-component-number.test.ts @@ -0,0 +1,52 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdComponentNumber. + */ + +import { ScvdComponentNumber } from '../../../model/scvd-component-number'; + +describe('ScvdComponentNumber', () => { + it('returns undefined for out-of-range values', () => { + const component = new ScvdComponentNumber(undefined); + expect(component.getComponentRange(-1)).toBeUndefined(); + expect(component.getComponentRange(0x100)).toBeUndefined(); + }); + + it('classifies component ranges', () => { + const component = new ScvdComponentNumber(undefined); + expect(component.getComponentRange(0x00)).toBe('User application software component'); + expect(component.getComponentRange(0x40)).toBe('Third party middleware component'); + expect(component.getComponentRange(0x80)).toBe('MDK-Middleware component'); + expect(component.getComponentRange(Number.NaN)).toBeUndefined(); + expect(component.getComponentRange(0xEE)).toBe('Fault component'); + expect(component.getComponentRange(0xEF)).toBe('Event statistics start/stop'); + expect(component.getComponentRange(0xF0)).toBe('RTOS kernel'); + expect(component.getComponentRange(0xFC)).toBe('RTOS kernel'); + expect(component.getComponentRange(0xFD)).toBe('Inter-process communication layer'); + expect(component.getComponentRange(0xFE)).toBe('printf-style debug output'); + expect(component.getComponentRange(0xFF)).toBe('Event Recorder message'); + }); + + it('stores component numbers', () => { + const component = new ScvdComponentNumber(undefined); + component.componentNumber = 3; + expect(component.componentNumber).toBe(3); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-component-viewer.test.ts b/src/views/component-viewer/test/unit/model/scvd-component-viewer.test.ts new file mode 100644 index 00000000..3d919045 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-component-viewer.test.ts @@ -0,0 +1,137 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdComponentViewer. + */ + +import { ExecutionContext } from '../../../scvd-eval-context'; +import { ScvdBreaks } from '../../../model/scvd-break'; +import { ScvdComponentIdentifier } from '../../../model/scvd-component-identifier'; +import { ScvdComponentViewer } from '../../../model/scvd-component-viewer'; +import { ScvdObjects } from '../../../model/scvd-object'; +import { ScvdTypedefs } from '../../../model/scvd-typedef'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdComponentViewer', () => { + it('returns false when XML is undefined', () => { + const viewer = new ScvdComponentViewer(undefined); + expect(viewer.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('returns false when component_viewer is missing', () => { + const viewer = new ScvdComponentViewer(undefined); + expect(viewer.readXml({})).toBe(false); + }); + + it('reads component viewer sections and breaks', () => { + const viewer = new ScvdComponentViewer(undefined); + const componentSpy = jest.spyOn(ScvdComponentIdentifier.prototype, 'readXml').mockReturnValue(true); + const objectsSpy = jest.spyOn(ScvdObjects.prototype, 'readXml').mockReturnValue(true); + const typedefsSpy = jest.spyOn(ScvdTypedefs.prototype, 'readXml').mockReturnValue(true); + const breaksSpy = jest.spyOn(ScvdBreaks.prototype, 'readXml').mockReturnValue(true); + + const xml = { + component_viewer: { + component: { name: 'comp' }, + objects: { object: [] }, + typedefs: { typedef: [] }, + break: [{ name: 'b1' }], + nested: { break: [{ name: 'b2' }] } + } + }; + + expect(viewer.readXml(xml)).toBe(true); + expect(viewer.component).toBeInstanceOf(ScvdComponentIdentifier); + expect(viewer.objects).toBeInstanceOf(ScvdObjects); + expect(viewer.typedefs).toBeInstanceOf(ScvdTypedefs); + expect(viewer.breaks).toBeInstanceOf(ScvdBreaks); + + expect(componentSpy).toHaveBeenCalledTimes(1); + expect(objectsSpy).toHaveBeenCalledTimes(1); + expect(typedefsSpy).toHaveBeenCalledTimes(1); + expect(breaksSpy).toHaveBeenCalledTimes(1); + + componentSpy.mockRestore(); + objectsSpy.mockRestore(); + typedefsSpy.mockRestore(); + breaksSpy.mockRestore(); + }); + + it('skips optional sections when they are missing', () => { + const viewer = new ScvdComponentViewer(undefined); + const xml = { component_viewer: { component: 'bad', objects: 5, typedefs: null } }; + + expect(viewer.readXml(xml)).toBe(true); + expect(viewer.component).toBeUndefined(); + expect(viewer.objects).toBeUndefined(); + expect(viewer.typedefs).toBeUndefined(); + expect(viewer.events).toBeUndefined(); + expect(viewer.breaks).toBeUndefined(); + expect(viewer.getSymbol('missing')).toBeUndefined(); + }); + + it('collects breaks from array entries and ignores non-objects', () => { + const viewer = new ScvdComponentViewer(undefined); + const collect = viewer as unknown as { collectBreakStatements: (node: unknown) => unknown[] }; + expect(collect.collectBreakStatements(undefined)).toEqual([]); + const breaks = collect.collectBreakStatements([ + { break: [{ name: 'b1' }] }, + 5, + { nested: [{ break: [{ name: 'b2' }] }] } + ]); + + expect(breaks).toHaveLength(2); + }); + + it('configures and validates all nodes', () => { + const viewer = new ScvdComponentViewer(undefined); + const child = new ScvdComponentViewer(viewer); + + const configureSpy = jest.spyOn(child, 'configure'); + expect(viewer.configureAll()).toBe(true); + expect(configureSpy).toHaveBeenCalledTimes(1); + + const validateSpy = jest.spyOn(child, 'validate').mockReturnValue(true); + expect(viewer.validateAll(true)).toBe(true); + expect(validateSpy).toHaveBeenCalledTimes(1); + }); + + it('calculates typedefs when present', async () => { + const viewer = new ScvdComponentViewer(undefined); + const typedefs = new ScvdTypedefs(viewer); + const calcSpy = jest.spyOn(typedefs, 'calculateTypedefs').mockResolvedValue(); + (viewer as unknown as { _typedefs?: ScvdTypedefs })._typedefs = typedefs; + + await expect(viewer.calculateTypedefs()).resolves.toBe(false); + + typedefs.addTypedef(); + await expect(viewer.calculateTypedefs()).resolves.toBe(true); + expect(calcSpy).toHaveBeenCalledTimes(1); + }); + + it('sets execution context recursively', () => { + const viewer = new ScvdComponentViewer(undefined); + const child = new ScvdComponentViewer(viewer); + const ctx = { evalContext: {} } as ExecutionContext; + + const setSpy = jest.spyOn(child, 'setExecutionContext'); + viewer.setExecutionContextAll(ctx); + expect(setSpy).toHaveBeenCalledWith(ctx); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-component.test.ts b/src/views/component-viewer/test/unit/model/scvd-component.test.ts new file mode 100644 index 00000000..b8924890 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-component.test.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdComponent. + */ + +import { ScvdComponent } from '../../../model/scvd-component'; +import { Json } from '../../../model/scvd-base'; +import { ScvdEventState } from '../../../model/scvd-event-state'; + +describe('ScvdComponent', () => { + it('returns false when XML is undefined', () => { + const component = new ScvdComponent(undefined); + expect(component.readXml(undefined as unknown as Json)).toBe(false); + expect(component.tag).toBe('XML undefined'); + }); + + it('reads component attributes and state list', () => { + const component = new ScvdComponent(undefined); + const stateSpy = jest.spyOn(ScvdEventState.prototype, 'readXml').mockReturnValue(true); + + const xml = { + brief: 'Brief', + no: '3', + prefix: 'pfx', + state: [{}, {}] + }; + + expect(component.readXml(xml)).toBe(true); + expect(component.brief).toBe('Brief'); + expect(component.no).toBe(3); + expect(component.prefix).toBe('pfx'); + expect(component.state).toHaveLength(2); + expect(component.addState()).toBeInstanceOf(ScvdEventState); + expect(component.state).toHaveLength(3); + + stateSpy.mockRestore(); + }); + + it('ignores undefined numbers', () => { + const component = new ScvdComponent(undefined); + component.no = undefined; + expect(component.no).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-condition.test.ts b/src/views/component-viewer/test/unit/model/scvd-condition.test.ts new file mode 100644 index 00000000..8b122d0e --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-condition.test.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdCondition. + */ + +import { ScvdCondition } from '../../../model/scvd-condition'; +import { ScvdExpression } from '../../../model/scvd-expression'; +import { ExecutionContext } from '../../../scvd-eval-context'; + +describe('ScvdCondition', () => { + it('defaults to true when no expression is defined', async () => { + const condition = new ScvdCondition(undefined); + await expect(condition.getResult()).resolves.toBe(true); + }); + + it('evaluates falsy and truthy values', async () => { + const condition = new ScvdCondition(undefined, 'expr'); + const getValueSpy = jest.spyOn(ScvdExpression.prototype, 'getValue'); + + getValueSpy.mockResolvedValueOnce(undefined); + await expect(condition.getResult()).resolves.toBe(false); + + getValueSpy.mockResolvedValueOnce(0); + await expect(condition.getResult()).resolves.toBe(false); + + getValueSpy.mockResolvedValueOnce(0n); + await expect(condition.getResult()).resolves.toBe(false); + + getValueSpy.mockResolvedValueOnce(1); + await expect(condition.getResult()).resolves.toBe(true); + + getValueSpy.mockResolvedValueOnce(2n); + await expect(condition.getResult()).resolves.toBe(true); + + getValueSpy.mockRestore(); + }); + + it('handles expression evaluation errors', async () => { + const condition = new ScvdCondition(undefined, 'expr'); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(ScvdExpression.prototype, 'getValue').mockRejectedValue(new Error('fail')); + + await expect(condition.getResult()).resolves.toBe(false); + + errorSpy.mockRestore(); + }); + + it('updates expressions and forwards execution context', () => { + const condition = new ScvdCondition(undefined, 'expr'); + const original = condition.expression; + + condition.expression = 'next'; + expect(condition.expression).toBe(original); + + const ctx = { evalContext: {} } as ExecutionContext; + const ctxSpy = jest.spyOn(ScvdExpression.prototype, 'setExecutionContext'); + condition.setExecutionContext(ctx); + + expect(ctxSpy).toHaveBeenCalledWith(ctx); + ctxSpy.mockRestore(); + }); + + it('creates expressions when none exist', () => { + const condition = new ScvdCondition(undefined); + condition.expression = 'created'; + expect(condition.expression).toBeInstanceOf(ScvdExpression); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-data-type.test.ts b/src/views/component-viewer/test/unit/model/scvd-data-type.test.ts new file mode 100644 index 00000000..ed25c439 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-data-type.test.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdDataType. + */ + +import { ScvdComplexDataType, ScvdDataType, ScvdScalarDataType } from '../../../model/scvd-data-type'; +import { ScvdNode } from '../../../model/scvd-node'; +import { ScvdTypedef } from '../../../model/scvd-typedef'; + +class DummyTypedef extends ScvdTypedef { + constructor() { + super(undefined); + } + + public override getTypeSize(): number | undefined { + return 8; + } + + public override getVirtualSize(): number | undefined { + return 12; + } + + public override getMember(_property: string): ScvdNode | undefined { + return this; + } +} + +describe('ScvdDataType', () => { + it('handles scalar and pointer scalar types', () => { + const scalar = new ScvdDataType(undefined, 'uint32_t'); + expect(scalar.type).toBeInstanceOf(ScvdScalarDataType); + expect(scalar.getTypeSize()).toBe(4); + expect(scalar.getVirtualSize()).toBe(4); + expect(scalar.getIsPointer()).toBe(false); + expect(scalar.getValueType()).toBe('uint32_t'); + expect(scalar.getMember('anything')).toBeUndefined(); + + const pointer = new ScvdDataType(undefined, '*uint16_t'); + expect(pointer.getIsPointer()).toBe(true); + expect(pointer.getValueType()).toBe('uint32_t'); + }); + + it('creates complex data types for custom names', () => { + const complex = new ScvdDataType(undefined, 'CustomType'); + expect(complex.type).toBeInstanceOf(ScvdComplexDataType); + expect(complex.getIsPointer()).toBe(false); + expect(complex.getValueType()).toBeUndefined(); + }); + + it('handles unknown scalar types without size info', () => { + const scalar = new ScvdScalarDataType(undefined, 'unknown_t'); + expect(scalar.type).toBeUndefined(); + expect(scalar.getTypeSize()).toBeUndefined(); + expect(scalar.getVirtualSize()).toBeUndefined(); + }); + + it('returns defaults when no type is provided', () => { + const dataType = new ScvdDataType(undefined, undefined); + expect(dataType.type).toBeUndefined(); + expect(dataType.getIsPointer()).toBe(false); + expect(dataType.getValueType()).toBeUndefined(); + + const scalar = new ScvdScalarDataType(undefined, undefined); + expect(scalar.type).toBeUndefined(); + expect(scalar.getIsPointer()).toBe(false); + }); + + it('fails to resolve complex types when no symbol is found', () => { + const missing = new ScvdComplexDataType(undefined, 'MissingType'); + const resolved = missing.resolveAndLink(() => undefined); + expect(resolved).toBe(false); + expect(missing.getTypeSize()).toBeUndefined(); + expect(missing.getVirtualSize()).toBeUndefined(); + + const noName = new ScvdComplexDataType(undefined, undefined); + expect(noName.resolveAndLink(() => undefined)).toBe(false); + }); + + it('resolves complex types and reflects pointer semantics', () => { + const typedef = new DummyTypedef(); + const complex = new ScvdComplexDataType(undefined, '*MyType'); + const ok = complex.resolveAndLink((name) => { + if (name === 'MyType') { + return typedef; + } + return undefined; + }); + expect(ok).toBe(true); + expect(complex.getIsPointer()).toBe(true); + expect(complex.getTypeSize()).toBe(8); + expect(complex.getVirtualSize()).toBe(12); + expect(complex.getMember('member')).toBe(typedef); + + const dataType = new ScvdDataType(undefined, '*MyType'); + const inner = dataType.type as ScvdComplexDataType; + inner.resolveAndLink((name) => (name === 'MyType' ? typedef : undefined)); + expect(dataType.getValueType()).toBe('uint32_t'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-endian.test.ts b/src/views/component-viewer/test/unit/model/scvd-endian.test.ts new file mode 100644 index 00000000..9a82b57a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-endian.test.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEndian. + */ + +import { ScvdEndian } from '../../../model/scvd-endian'; + +describe('ScvdEndian', () => { + it('defaults to little-endian and does no conversion', () => { + const endian = new ScvdEndian(undefined); + expect(endian.endian).toBe('L'); + expect(endian.isBigEndian).toBe(false); + expect(endian.convertToBigEndian(0x1234)).toBe(0x1234); + }); + + it('handles big-endian conversion and tracks modifications', () => { + const endian = new ScvdEndian(undefined, 'B'); + expect(endian.isBigEndian).toBe(true); + expect(endian.convertToBigEndian(0x1234)).toBe(0x3412); + + endian.endian = 'L'; + expect(endian.isBigEndian).toBe(false); + expect(endian.isModified).toBe(true); + }); + + it('treats unknown endianness as little-endian', () => { + const endian = new ScvdEndian(undefined, 'X'); + expect(endian.isBigEndian).toBe(false); + expect(endian.convertToBigEndian(0xABCD)).toBe(0xABCD); + }); + + it('falls back to zero when the hex match fails', () => { + const endian = new ScvdEndian(undefined, 'B'); + const badValue = { toString: () => '' } as unknown as number; + expect(endian.convertToBigEndian(badValue)).toBe(0); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-enum.test.ts b/src/views/component-viewer/test/unit/model/scvd-enum.test.ts new file mode 100644 index 00000000..9cd9ba96 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-enum.test.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEnum. + */ + +import { ScvdEnum } from '../../../model/scvd-enum'; +import { Json } from '../../../model/scvd-base'; +import { ScvdExpression } from '../../../model/scvd-expression'; + +describe('ScvdEnum', () => { + it('initializes value from previous enum', () => { + const parent = new ScvdEnum(undefined, undefined); + const next = new ScvdEnum(undefined, parent); + + expect(parent.value.expression).toBe('0'); + expect(next.value.expression).toBe('(0) + 1'); + }); + + it('reads XML and replaces the expression', () => { + const item = new ScvdEnum(undefined, undefined); + const readSpy = jest.spyOn(ScvdExpression.prototype, 'readXml').mockReturnValue(true); + + const xml = { value: '5' }; + expect(item.readXml(xml)).toBe(true); + expect(item.value).toBeInstanceOf(ScvdExpression); + expect(item.value.expression).toBe('5'); + + readSpy.mockRestore(); + }); + + it('returns false when XML is undefined', () => { + const item = new ScvdEnum(undefined, undefined); + expect(item.readXml(undefined as unknown as Json)).toBe(false); + expect(item.tag).toBe('XML undefined'); + }); + + it('ignores undefined value updates', () => { + const item = new ScvdEnum(undefined, undefined); + const current = item.value; + item.value = undefined; + expect(item.value).toBe(current); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-event-id.test.ts b/src/views/component-viewer/test/unit/model/scvd-event-id.test.ts new file mode 100644 index 00000000..baedd086 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-event-id.test.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEventId. + */ + +import { ParseResult } from '../../../parser-evaluator/parser'; +import { ScvdEventId } from '../../../model/scvd-event-id'; + +describe('ScvdEventId', () => { + const makeAst = (constValue: ParseResult['constValue']): ParseResult => ({ + ast: {} as ParseResult['ast'], + diagnostics: [], + externalSymbols: [], + isPrintf: false, + constValue + }); + + it('derives message, component, and level from numeric const values', () => { + const eventId = new ScvdEventId(undefined, '0x12345'); + eventId.id.expressionAst = makeAst(0x12345); + + expect(eventId.configure()).toBe(true); + expect(eventId.messageNumber).toBe(0x45); + expect(eventId.componentNumber).toBe(0x23); + expect(eventId.level).toBe(0x1); + }); + + it('ignores non-numeric constant values', () => { + const eventId = new ScvdEventId(undefined, 'ID'); + eventId.id.expressionAst = makeAst('ID'); + + eventId.configure(); + expect(eventId.messageNumber).toBeUndefined(); + expect(eventId.componentNumber).toBeUndefined(); + expect(eventId.level).toBeUndefined(); + }); + + it('returns the validation result unchanged', () => { + const eventId = new ScvdEventId(undefined, '1'); + expect(eventId.validate(true)).toBe(true); + expect(eventId.validate(false)).toBe(false); + }); + + it('handles missing id expressions', () => { + const eventId = new ScvdEventId(undefined, '1'); + (eventId as unknown as { _id?: unknown })._id = undefined; + expect(eventId.configure()).toBe(true); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-event-level.test.ts b/src/views/component-viewer/test/unit/model/scvd-event-level.test.ts new file mode 100644 index 00000000..063c2a41 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-event-level.test.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEventLevel. + */ + +import { EventLevel, EventLevelMap, EventLevelReverseMap, ScvdEventLevel } from '../../../model/scvd-event-level'; + +describe('ScvdEventLevel', () => { + it('maps between string and enum values', () => { + expect(EventLevelMap.get('Error')).toBe(EventLevel.EventLevelError); + expect(EventLevelReverseMap.get(EventLevel.EventLevelAPI)).toBe('API'); + }); + + it('assigns levels via map and enum keys', () => { + const level = new ScvdEventLevel(undefined, 'Error'); + expect(level.level).toBe(EventLevel.EventLevelError); + + level.level = 'EventLevelDetail'; + expect(level.level).toBe(EventLevel.EventLevelDetail); + }); + + it('handles unknown level strings', () => { + const level = new ScvdEventLevel(undefined, 'Missing'); + expect(level.level).toBeUndefined(); + expect(level.filterLevel(EventLevel.EventLevelDetail)).toBe(true); + + level.level = 'AlsoMissing'; + expect(level.level).toBeUndefined(); + + level.level = undefined; + expect(level.level).toBeUndefined(); + }); + + it('filters levels appropriately', () => { + const level = new ScvdEventLevel(undefined, 'API'); + expect(level.filterLevel(EventLevel.EventLevelDetail)).toBe(true); + expect(level.filterLevel(EventLevel.EventLevelError)).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-event-state.test.ts b/src/views/component-viewer/test/unit/model/scvd-event-state.test.ts new file mode 100644 index 00000000..5d0c8823 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-event-state.test.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEventState. + */ + +import { ScvdEventState } from '../../../model/scvd-event-state'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdEventState', () => { + it('returns false when XML is undefined', () => { + const state = new ScvdEventState(undefined); + expect(state.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads valid values from XML', () => { + const state = new ScvdEventState(undefined); + const xml = { + plot: 'line', + color: 'red', + unique: 'true', + dormant: 'true', + ssel: 'false' + }; + + expect(state.readXml(xml)).toBe(true); + expect(state.plot).toBe('line'); + expect(state.color).toBe('red'); + expect(state.unique).toBe(true); + expect(state.dormant).toBe(true); + expect(state.ssel).toBe(false); + }); + + it('ignores invalid plot and color updates', () => { + const state = new ScvdEventState(undefined); + state.plot = 'box'; + state.color = 'green'; + + state.plot = 'invalid'; + state.color = 'orange'; + + expect(state.plot).toBe('box'); + expect(state.color).toBe('green'); + }); + + it('updates boolean flags from string and boolean values', () => { + const state = new ScvdEventState(undefined); + state.unique = 'true'; + state.dormant = 'false'; + state.ssel = true; + + expect(state.unique).toBe(true); + expect(state.dormant).toBe(false); + expect(state.ssel).toBe(true); + }); + + it('ignores undefined boolean updates', () => { + const state = new ScvdEventState(undefined); + state.unique = true; + state.dormant = true; + state.ssel = false; + + state.unique = undefined; + state.dormant = undefined; + state.ssel = undefined; + + expect(state.unique).toBe(true); + expect(state.dormant).toBe(true); + expect(state.ssel).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-event-tracking.test.ts b/src/views/component-viewer/test/unit/model/scvd-event-tracking.test.ts new file mode 100644 index 00000000..a42548b2 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-event-tracking.test.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEventTracking. + */ + +import { ScvdEventTracking, ScvdEventTrackingMode } from '../../../model/scvd-event-tracking'; + +describe('ScvdEventTracking', () => { + it('maps string modes to enum values', () => { + const tracking = new ScvdEventTracking(undefined, 'Start'); + expect(tracking.mode).toBe(ScvdEventTrackingMode.Start); + + tracking.mode = 'Stop'; + expect(tracking.mode).toBe(ScvdEventTrackingMode.Stop); + + tracking.mode = 'Reset'; + expect(tracking.mode).toBe(ScvdEventTrackingMode.Reset); + }); + + it('ignores undefined or unknown mode values', () => { + const tracking = new ScvdEventTracking(undefined, 'Start'); + tracking.mode = undefined; + expect(tracking.mode).toBe(ScvdEventTrackingMode.Start); + + tracking.mode = 'Unknown'; + expect(tracking.mode).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-event.test.ts b/src/views/component-viewer/test/unit/model/scvd-event.test.ts new file mode 100644 index 00000000..4e730455 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-event.test.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEvent. + */ + +import { Json } from '../../../model/scvd-base'; +import { ScvdEvent } from '../../../model/scvd-event'; +import { ScvdEventTrackingMode } from '../../../model/scvd-event-tracking'; +import { ScvdEventState } from '../../../model/scvd-event-state'; + +describe('ScvdEvent', () => { + it('reads XML and populates event fields', () => { + const event = new ScvdEvent(undefined); + expect(event.readXml(undefined as unknown as Json)).toBe(false); + + const xml: Json = { + id: '1', + level: '1', + property: 'prop', + value: 'val', + doc: 'doc', + handle: '2', + hname: 'hname', + state: 'ready', + tracking: 'Start', + print: { name: 'print', value: '1' } + }; + + expect(event.readXml(xml)).toBe(true); + expect(event.id).toBeDefined(); + expect(event.level).toBeDefined(); + expect(event.property).toBeDefined(); + expect(event.value).toBeDefined(); + expect(event.doc).toBe('doc'); + expect(event.handle).toBe(2); + expect(event.hname).toBeDefined(); + expect(event.stateName).toBe('ready'); + expect(event.tracking?.mode).toBe(ScvdEventTrackingMode.Start); + expect(event.print).toHaveLength(1); + }); + + it('updates nested objects via setters', () => { + const event = new ScvdEvent(undefined); + event.level = '1'; + event.level = '2'; + expect(event.level?.level).toBe('EventLevelOp'); + + event.property = 'A'; + event.property = 'B'; + expect(event.property?.expression?.expression).toBe('B'); + + event.tracking = 'Start'; + event.tracking = 'Stop'; + expect(event.tracking?.mode).toBe(ScvdEventTrackingMode.Stop); + + const state = new ScvdEventState(undefined); + event.state = state; + expect(event.state).toBe(state); + event.state = undefined; + expect(event.state).toBeUndefined(); + event.stateName = 'next'; + expect(event.stateName).toBe('next'); + }); + + it('adds print items and resolves links with default behavior', () => { + const event = new ScvdEvent(undefined); + const printItem = event.addPrint(); + expect(event.print).toContain(printItem); + expect(event.resolveAndLink(() => undefined)).toBe(false); + }); + + it('ignores undefined setter values', () => { + const event = new ScvdEvent(undefined); + event.id = undefined; + event.level = undefined; + event.property = undefined; + event.value = undefined; + event.handle = undefined; + event.hname = undefined; + event.tracking = undefined; + expect(event.id).toBeUndefined(); + expect(event.level).toBeUndefined(); + expect(event.property).toBeUndefined(); + expect(event.value).toBeUndefined(); + expect(event.handle).toBeUndefined(); + expect(event.hname).toBeUndefined(); + expect(event.tracking).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-events.test.ts b/src/views/component-viewer/test/unit/model/scvd-events.test.ts new file mode 100644 index 00000000..2e12d5a3 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-events.test.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEvents. + */ + +import { ScvdEvent } from '../../../model/scvd-event'; +import { ScvdEvents } from '../../../model/scvd-events'; +import { ScvdGroup } from '../../../model/scvd-group'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdEvents', () => { + it('returns false when XML is undefined', () => { + const events = new ScvdEvents(undefined); + expect(events.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('creates event and group entries from xml', () => { + const events = new ScvdEvents(undefined); + const eventSpy = jest.spyOn(ScvdEvent.prototype, 'readXml').mockReturnValue(true); + const groupSpy = jest.spyOn(ScvdGroup.prototype, 'readXml').mockReturnValue(true); + + const xml = { + event: { id: 'evt' }, + group: { name: 'group' } + }; + + expect(events.readXml(xml)).toBe(true); + expect(events.event).toHaveLength(1); + expect(events.group).toHaveLength(1); + expect(eventSpy).toHaveBeenCalledTimes(1); + expect(groupSpy).toHaveBeenCalledTimes(1); + + eventSpy.mockRestore(); + groupSpy.mockRestore(); + }); + + it('adds events and groups directly', () => { + const events = new ScvdEvents(undefined); + const event = events.addEvent(); + const group = events.addGroup(); + + expect(events.event).toEqual([event]); + expect(events.group).toEqual([group]); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-expression.test.ts b/src/views/component-viewer/test/unit/model/scvd-expression.test.ts new file mode 100644 index 00000000..779e1c15 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-expression.test.ts @@ -0,0 +1,150 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdExpression. + */ + +import { ParseResult } from '../../../parser-evaluator/parser'; +import { EvaluateResult } from '../../../parser-evaluator/evaluator'; +import { ExecutionContext } from '../../../scvd-eval-context'; + +jest.mock('../../../parser-evaluator/parser', () => ({ + parseExpression: jest.fn() +})); + +jest.mock('../../../parser-evaluator/evaluator', () => ({ + evaluateParseResult: jest.fn() +})); + +import { parseExpression } from '../../../parser-evaluator/parser'; +import { evaluateParseResult } from '../../../parser-evaluator/evaluator'; +import { ScvdExpression } from '../../../model/scvd-expression'; + +describe('ScvdExpression', () => { + const makeAst = (overrides: Partial): ParseResult => ({ + ast: {} as ParseResult['ast'], + diagnostics: [], + externalSymbols: [], + isPrintf: false, + ...overrides + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('parses expressions and evaluates constant branches', async () => { + const expr = new ScvdExpression(undefined, '1', 'value'); + const ast = makeAst({ constValue: 7 }); + (parseExpression as jest.Mock).mockReturnValue(ast); + + expect(expr.configure()).toBe(true); + expect(expr.expressionAst).toBe(ast); + await expect(expr.evaluate()).resolves.toBe(7); + + expr.expressionAst = makeAst({ constValue: true }); + await expect(expr.evaluate()).resolves.toBe(1); + + expr.expressionAst = makeAst({ constValue: false }); + await expect(expr.evaluate()).resolves.toBe(0); + }); + + it('reads and updates the scvd variable name', () => { + const expr = new ScvdExpression(undefined, '1', 'value'); + expect(expr.scvdVarName).toBe('value'); + expr.scvdVarName = 'next'; + expect(expr.scvdVarName).toBe('next'); + }); + + it('evaluates non-constant expressions using execution context', async () => { + const expr = new ScvdExpression(undefined, 'X', 'value'); + const ast = makeAst({ constValue: undefined }); + expr.expressionAst = ast; + (evaluateParseResult as jest.Mock).mockResolvedValue(42 as EvaluateResult); + + const ctx = { evalContext: {} } as ExecutionContext; + expr.setExecutionContext(ctx); + await expect(expr.getValue()).resolves.toBe(42); + }); + + it('handles missing AST or context during evaluation', async () => { + const expr = new ScvdExpression(undefined, 'X', 'value'); + expr.expressionAst = makeAst({ constValue: undefined }); + await expect(expr.evaluate()).resolves.toBeUndefined(); + + expr.setExecutionContext({ evalContext: {} } as ExecutionContext); + expr.expressionAst = undefined; + await expect(expr.evaluate()).resolves.toBeUndefined(); + }); + + it('returns stringified results for numbers, bigints, and strings', async () => { + const expr = new ScvdExpression(undefined, 'X', 'value'); + expr.expressionAst = makeAst({ constValue: 5 }); + await expect(expr.getResultString()).resolves.toBe('5'); + + expr.expressionAst = makeAst({ constValue: 'str' }); + await expect(expr.getResultString()).resolves.toBe('str'); + + expr.expressionAst = makeAst({ constValue: undefined }); + expr.setExecutionContext({ evalContext: {} } as ExecutionContext); + (evaluateParseResult as jest.Mock) + .mockResolvedValueOnce(5n as EvaluateResult) + .mockResolvedValueOnce({} as EvaluateResult); + await expect(expr.getResultString()).resolves.toBe('5'); + + expr.expressionAst = makeAst({ constValue: undefined }); + await expect(expr.getResultString()).resolves.toBeUndefined(); + }); + + it('parses expressions and validates diagnostics', () => { + const expr = new ScvdExpression(undefined, undefined, 'value'); + expect(expr.configure()).toBe(true); + expect(expr.validate(true)).toBe(false); + + expr.expression = '1'; + expr.expressionAst = undefined; + expect(expr.validate(true)).toBe(false); + + expr.expressionAst = makeAst({ + diagnostics: [{ type: 'error', message: 'error', start: 0, end: 1 }] + }); + expect(expr.validate(true)).toBe(false); + + expr.expressionAst = makeAst({}); + expect(expr.validate(true)).toBe(true); + }); + + it('skips parsing when an AST already exists', () => { + const expr = new ScvdExpression(undefined, '1', 'value'); + const ast = makeAst({ constValue: 1 }); + expr.expressionAst = ast; + expect(expr.configure()).toBe(true); + expect(expr.expressionAst).toBe(ast); + }); + + it('does not set AST when parse diagnostics exist', () => { + const expr = new ScvdExpression(undefined, '1', 'value'); + const ast = makeAst({ + diagnostics: [{ type: 'error', message: 'err', start: 0, end: 1 }] + }); + (parseExpression as jest.Mock).mockReturnValue(ast); + expect(expr.configure()).toBe(true); + expect(expr.expressionAst).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-format-specifier.test.ts b/src/views/component-viewer/test/unit/model/scvd-format-specifier.test.ts new file mode 100644 index 00000000..2a6af15f --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-format-specifier.test.ts @@ -0,0 +1,275 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdFormatSpecifier. + */ + +import { ScvdFormatSpecifier } from '../../../model/scvd-format-specifier'; + +describe('ScvdFormatSpecifier', () => { + const formatter = new ScvdFormatSpecifier(); + + it('formats numbers by type and bit width', () => { + expect(formatter.formatNumberByType(3.7, { kind: 'int' })).toBe('3'); + expect(formatter.formatNumberByType(-1, { kind: 'uint', bits: 8 })).toBe('256'); + expect(formatter.formatNumberByType(0xFF, { kind: 'int', bits: 8 })).toBe('-1'); + expect(formatter.formatNumberByType(1.23456, { kind: 'float', bits: 32 })).toBe('1.235'); + expect(formatter.formatNumberByType(42, { kind: 'uint', bits: 64 })).toBe('42'); + expect(formatter.formatNumberByType(5n, { kind: 'uint', bits: 64 })).toBe('5'); + expect(formatter.formatNumberByType(10n, { kind: 'uint' })).toBe('10'); + expect(formatter.formatNumberByType(-5n, { kind: 'int' })).toBe('-5'); + expect(formatter.formatNumberByType(7n, { kind: 'unknown' })).toBe('7'); + expect(formatter.formatNumberByType(0xFFn, { kind: 'int', bits: 8 })).toBe('-1'); + expect(formatter.formatNumberByType(0x7Fn, { kind: 'int', bits: 8 })).toBe('127'); + expect(formatter.formatNumberByType(0xFFFF, { kind: 'int', bits: 16 })).toBe('-1'); + expect(formatter.formatNumberByType(0xFFFF, { kind: 'uint', bits: 16 })).toBe('0'); + expect(formatter.formatNumberByType(3.14, { kind: 'unknown' })).toBe('3.14'); + expect(formatter.formatNumberByType(Number.NaN, { kind: 'uint', bits: 64 })).toBe('NaN'); + expect(formatter.formatNumberByType(Number.POSITIVE_INFINITY, { kind: 'int' })).toBe('Infinity'); + }); + + it('formats hex with padding and finite checks', () => { + expect(formatter.formatHex(15, 8, true)).toBe('0x0f'); + expect(formatter.formatHex(9, 8)).toBe('0x9'); + expect(formatter.formatHex(15n, 8, true)).toBe('0x0f'); + expect(formatter.formatHex(1, 128, true)).toBe('0x0000000000000001'); + expect(formatter.formatHex(1n, { kind: 'int', bits: 16 }, false)).toBe('0x1'); + expect(formatter.formatHex(1, undefined, false)).toBe('0x1'); + expect(formatter.formatHex(2, { kind: 'int' }, false)).toBe('0x2'); + expect(formatter.formatHex(3, 0, true)).toBe('0x3'); + expect(formatter.formatHex(3n, undefined, true)).toBe('0x3'); + expect(formatter.formatHex(Number.NaN, 8, true)).toBe('NaN'); + }); + + it('formats base specifiers', () => { + expect(formatter.format('d', 4.9)).toBe('4'); + expect(formatter.format('d', true)).toBe('1'); + expect(formatter.format('d', { bad: 'value' } as unknown as number)).toBe('NaN'); + expect(formatter.format('d', 5, { typeInfo: { kind: 'int', bits: 16 } })).toBe('5'); + expect(formatter.format('d', 5n)).toBe('5'); + expect(formatter.format('u', -1.9)).toBe('4294967295'); + expect(formatter.format('u', false)).toBe('0'); + expect(formatter.format('u', 5n)).toBe('5'); + expect(formatter.format('x', 15, { typeInfo: { kind: 'int', bits: 8 }, padHex: true })).toBe('0x0f'); + expect(formatter.format('x', '5')).toBe('0x5'); + expect(formatter.format('x', 'bad')).toBe('NaN'); + expect(formatter.format('x', 10n)).toBe('0xa'); + expect(formatter.format('t', 'ok %% text')).toBe('ok %% text'); + expect(formatter.format('t', 'bad %s')).toBe('bad literal - %t: text with embedded %format specifier(s)'); + expect(formatter.format('t', 5n)).toBe('5'); + expect(formatter.format('C', 1)).toMatch(/^0x/i); + expect(formatter.format('S', 'addr')).toBe('addr'); + expect(formatter.format('E', 5)).toBe('5'); + expect(formatter.format('E', 5, { enumText: '7' })).toBe('7'); + expect(formatter.format('E', 'bad')).toBe('bad'); + expect(formatter.format('N', 12)).toBe('12'); + expect(formatter.format('U', 'nope')).toBe('nope'); + expect(formatter.format('%', 0)).toBe('%'); + expect(formatter.format('q', 9, { allowUnknownSpec: true })).toBe(''); + expect(formatter.format('q', 9)).toBe('9'); + expect(formatter.format_u(5)).toBe('5'); + expect(formatter.format_d('bad')).toBe('bad'); + }); + + it('formats text and byte array variants', () => { + expect(formatter.format_t('hi')).toBe('hi'); + expect(formatter.format_t(4)).toBe('4'); + expect(formatter.format_t(new Uint8Array([65, 66]))).toBe('AB'); + expect(formatter.format_t({ bad: 'value' } as unknown as number)).toBe('[object Object]'); + expect(formatter.format_t(new Uint8Array([65, 0, 66]))).toBe('A'); + expect(formatter.format('t', new Uint8Array([65, 0, 66]))).toBe('A'); + expect(formatter.format('N', new Uint8Array([65, 66, 0, 67]))).toBe('AB'); + expect(formatter.format('U', new Uint8Array([65, 0, 0, 0]))).toBe('A'); + }); + + it('formats address-like and enum types', () => { + expect(formatter.format_address_like('0x123')).toBe('0x123'); + expect(formatter.format_address_like({} as number)).toBe(''); + expect(formatter.format_C(2)).toMatch(/^0x/i); + expect(formatter.format_S(3)).toMatch(/^0x/i); + expect(formatter.format_E('7')).toBe('7'); + }); + + it('formats IPv4/IPv6 and MAC values', () => { + expect(formatter.format_I('1.2.3.4')).toBe('1.2.3.4'); + expect(formatter.format_I(0x01020304)).toBe('1.2.3.4'); + expect(formatter.format_I(10n)).toBe('0.0.0.10'); + expect(formatter.format_I(Number.NaN)).toBe('NaN'); + expect(formatter.format('I', 0x01020304)).toBe('1.2.3.4'); + + const ipv4 = new Uint8Array([1, 2, 3, 4]); + const ipv4Short = new Uint8Array([1, 2, 3]); + expect(formatter.format('I', ipv4)).toBe('1.2.3.4'); + expect(formatter.format('I', ipv4Short)).toBe(''); + + const ipv6 = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 + ]); + const ipv6Short = new Uint8Array([0, 0, 0, 0]); + expect(formatter.format('J', ipv6)).toBe('::1'); + expect(formatter.format('J', ipv6Short)).toBe(''); + expect(formatter.format('J', 0x10)).toBe('0x10'); + expect(formatter.format_J('::1')).toBe('::1'); + expect(formatter.format_J(0x10)).toBe('0x10'); + + const ipv6Middle = new Uint8Array([ + 0x20, 0x01, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1 + ]); + expect(formatter.formatIpv6(ipv6Middle)).toBe('2001:0:0:1::1'); + + const ipv6End = new Uint8Array([ + 0, 1, 0, 2, 0, 3, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0 + ]); + expect(formatter.formatIpv6(ipv6End)).toBe('1:2:3:4::'); + + const ipv6NoCollapse = new Uint8Array([ + 0, 1, 0, 0, 0, 2, 0, 3, + 0, 4, 0, 5, 0, 6, 0, 7 + ]); + expect(formatter.formatIpv6(ipv6NoCollapse)).toBe('1:0:2:3:4:5:6:7'); + + const mac = new Uint8Array([0, 1, 2, 3, 4, 5]); + const macShort = new Uint8Array([0, 1]); + expect(formatter.format('M', mac)).toBe('00-01-02-03-04-05'); + expect(formatter.format('M', 0x010203040506)).toBe('05-06-03-04-05-06'); + expect(formatter.formatMac(macShort)).toBe(''); + expect(formatter.format_M(0x000102030405)).toBe('04-05-02-03-04-05'); + expect(formatter.format_M('AA:BB:CC')).toBe('00-00-00-AA-BB-CC'); + expect(formatter.format_M(Number.NaN)).toBe('NaN'); + + const savedMatch = String.prototype.match; + try { + String.prototype.match = function (): RegExpMatchArray | null { + return null; + }; + expect(formatter.format_M('AA')).toBe('AA'); + } finally { + String.prototype.match = savedMatch; + } + }); + + it('formats typed hex and float values', () => { + expect(formatter.format_T('3.14', { kind: 'float', bits: 32 })).toBe('3.140'); + expect(formatter.format_T(5.9, { kind: 'unknown' }, true)).toBe('5.90000'); + expect(formatter.format_T('bad', { kind: 'int' })).toBe('bad'); + expect(formatter.format_T(5, { kind: 'int', bits: 8 }, true)).toBe('0x05'); + expect(formatter.format_T(10, { kind: 'unknown' })).toBe('0xa'); + expect(formatter.format_T(5n, { kind: 'float', bits: 32 })).toBe('5.000'); + expect(formatter.format_T(10n, { kind: 'int', bits: 8 }, true)).toBe('0x0a'); + expect(formatter.format_T({ bad: 'value' })).toBe('[object Object]'); + expect(formatter.format('T', 255, { typeInfo: { kind: 'int', bits: 8 }, padHex: true })).toBe('0xff'); + }); + + it('sanitizes literals and escapes non-printable characters', () => { + expect(formatter.sanitizeLiteral('ok %% text')).toBe('ok %% text'); + expect(formatter.sanitizeLiteral('bad %s')).toBeUndefined(); + + const text = `A\n\r\t\v\f\b${String.fromCharCode(7)}${String.fromCharCode(1)}B\0C`; + const escaped = formatter.escapeNonPrintable(text); + expect(escaped).toContain('\\n'); + expect(escaped).toContain('\\r'); + expect(escaped).toContain('\\t'); + expect(escaped).toContain('\\v'); + expect(escaped).toContain('\\f'); + expect(escaped).toContain('\\b'); + expect(escaped).toContain('\\a'); + expect(escaped).toContain('\\01'); + expect(escaped).toContain('B'); + expect(escaped).not.toContain('C'); + expect(formatter.escapeNonPrintable(String.fromCharCode(0x80))).toBe('\\80'); + }); + + it('prints USB descriptors for supported types', () => { + expect(formatter.printUsbDescriptor(new Uint8Array([]))).toBe('(invalid: too short)'); + expect(formatter.printUsbDescriptor(new Uint8Array([1, 3]))).toBe('USB String: (invalid)'); + expect(formatter.printUsbDescriptor(new Uint8Array([2, 3]))).toBe('USB String: (empty)'); + + const str = new Uint8Array([6, 3, 65, 0, 66, 0]); + expect(formatter.printUsbDescriptor(str)).toBe('USB String: "AB"'); + + const device = new Uint8Array([18, 1, 0, 2, 3, 4, 5, 64, 1, 0, 2, 0, 3, 0, 4, 5, 6, 1]); + expect(formatter.printUsbDescriptor(device)).toContain('USB Device'); + + const config = new Uint8Array([9, 2, 9, 0, 1, 1, 0, 0x80, 50]); + expect(formatter.printUsbDescriptor(config)).toContain('USB Configuration'); + const configLengthFallback = new Uint8Array([0, 2, 9, 0, 1, 1, 0, 0x80, 50]); + expect(formatter.printUsbDescriptor(configLengthFallback)).toContain('USB Configuration'); + + const iface = new Uint8Array([9, 4, 1, 0, 2, 0xff, 0, 0, 0]); + expect(formatter.printUsbDescriptor(iface)).toContain('USB Interface'); + + const endpoint = new Uint8Array([7, 5, 0x81, 2, 64, 0, 10]); + expect(formatter.printUsbDescriptor(endpoint)).toContain('USB Endpoint'); + const endpointShort = new Uint8Array([2, 5]); + expect(formatter.printUsbDescriptor(endpointShort)).toContain('USB Endpoint'); + + const qualifier = new Uint8Array([10, 6, 0, 2, 0, 0, 0, 64, 1, 0]); + expect(formatter.printUsbDescriptor(qualifier)).toContain('USB Device Qualifier'); + + const bos = new Uint8Array([5, 0x0f, 5, 0, 1]); + expect(formatter.printUsbDescriptor(bos)).toContain('USB BOS'); + const bosShort = new Uint8Array([3, 0x0f, 5]); + expect(formatter.printUsbDescriptor(bosShort)).toContain('USB BOS'); + + const unknown = new Uint8Array([4, 0x99, 1, 2, 3]); + expect(formatter.printUsbDescriptor(unknown)).toContain('Type 0x99'); + + const savedU8At = Uint8Array.prototype.at; + const savedU16At = Uint16Array.prototype.at; + try { + Uint8Array.prototype.at = function (index: number): number | undefined { + if (index === 2 || index === 3) { + return undefined; + } + return savedU8At.call(this, index); + }; + Uint16Array.prototype.at = function (index: number): number | undefined { + if (index === 0) { + return undefined; + } + return savedU16At.call(this, index); + }; + const patched = formatter.printUsbDescriptor(str); + expect(patched).toContain('USB String: "'); + const patchedConfig = formatter.printUsbDescriptor(config); + expect(patchedConfig).toContain('USB Configuration'); + } finally { + Uint8Array.prototype.at = savedU8At; + Uint16Array.prototype.at = savedU16At; + } + }); + + it('covers fallback string decoding and non-finite hex', () => { + const globalAny = globalThis as { TextDecoder?: typeof TextDecoder }; + const savedDecoder = globalAny.TextDecoder; + delete globalAny.TextDecoder; + try { + expect(formatter.format_t(new Uint8Array([65, 66, 0]))).toBe('AB'); + } finally { + if (savedDecoder) { + globalAny.TextDecoder = savedDecoder; + } + } + expect(formatter.format_x(Number.NaN)).toBe('NaN'); + expect(formatter.format_u('bad')).toBe('bad'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-group.test.ts b/src/views/component-viewer/test/unit/model/scvd-group.test.ts new file mode 100644 index 00000000..b3b7c84f --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-group.test.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdGroup. + */ + +import { ScvdComponent } from '../../../model/scvd-component'; +import { ScvdGroup } from '../../../model/scvd-group'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdGroup', () => { + it('returns false when XML is undefined', () => { + const group = new ScvdGroup(undefined); + expect(group.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads components from XML', () => { + const group = new ScvdGroup(undefined); + const readSpy = jest.spyOn(ScvdComponent.prototype, 'readXml').mockReturnValue(true); + + const xml = { + component: [{ name: 'c1' }, { name: 'c2' }] + }; + + expect(group.readXml(xml)).toBe(true); + expect(group.components).toHaveLength(2); + expect(readSpy).toHaveBeenCalledTimes(2); + + readSpy.mockRestore(); + }); + + it('adds components directly', () => { + const group = new ScvdGroup(undefined); + const component = group.addComponent(); + + expect(group.components).toEqual([component]); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-gui-interface.test.ts b/src/views/component-viewer/test/unit/model/scvd-gui-interface.test.ts new file mode 100644 index 00000000..146c59e8 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-gui-interface.test.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdGuiInterface. + */ + +import { ScvdGuiInterface } from '../../../model/scvd-gui-interface'; + +class DummyGui implements ScvdGuiInterface { + public getGuiEntry(): { name: string | undefined; value: string | undefined } { + return { name: 'name', value: 'value' }; + } + + public getGuiChildren(): ScvdGuiInterface[] { + return []; + } + + public getGuiName(): string | undefined { + return 'name'; + } + + public getGuiValue(): string | undefined { + return 'value'; + } + + public getGuiConditionResult(): boolean { + return true; + } + + public getGuiLineInfo(): string | undefined { + return 'line'; + } + + public hasGuiChildren(): boolean { + return false; + } +} + +describe('ScvdGuiInterface', () => { + it('supports the expected interface shape', () => { + const gui = new DummyGui(); + expect(gui.getGuiEntry()).toEqual({ name: 'name', value: 'value' }); + expect(gui.getGuiChildren()).toEqual([]); + expect(gui.getGuiName()).toBe('name'); + expect(gui.getGuiValue()).toBe('value'); + expect(gui.getGuiConditionResult()).toBe(true); + expect(gui.getGuiLineInfo()).toBe('line'); + expect(gui.hasGuiChildren()).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-item.test.ts b/src/views/component-viewer/test/unit/model/scvd-item.test.ts new file mode 100644 index 00000000..4df2f598 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-item.test.ts @@ -0,0 +1,99 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdItem. + */ + +import { ScvdCondition } from '../../../model/scvd-condition'; +import { ScvdItem } from '../../../model/scvd-item'; +import { ScvdListOut } from '../../../model/scvd-list-out'; +import { ScvdPrint } from '../../../model/scvd-print'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdItem', () => { + it('returns false when XML is undefined', () => { + const item = new ScvdItem(undefined); + expect(item.readXml(undefined as unknown as Json)).toBe(false); + expect(item.tag).toBe('XML undefined'); + }); + + it('reads XML and builds child lists', () => { + const item = new ScvdItem(undefined); + const listSpy = jest.spyOn(ScvdListOut.prototype, 'readXml').mockReturnValue(true); + const printSpy = jest.spyOn(ScvdPrint.prototype, 'readXml').mockReturnValue(true); + + const xml = { + property: '1', + value: '2', + cond: '1', + bold: '0', + alert: '0', + item: [{}], + list: [{}], + print: [{}] + }; + + expect(item.readXml(xml)).toBe(true); + expect(item.property).toBeDefined(); + expect(item.value).toBeDefined(); + expect(item.cond).toBeInstanceOf(ScvdCondition); + expect(item.bold).toBeInstanceOf(ScvdCondition); + expect(item.alert).toBeInstanceOf(ScvdCondition); + expect(item.item).toHaveLength(1); + expect(item.listOut).toHaveLength(1); + expect(item.print).toHaveLength(1); + + expect(item.addItem()).toBeInstanceOf(ScvdItem); + expect(item.addListOut()).toBeInstanceOf(ScvdListOut); + expect(item.addPrint()).toBeInstanceOf(ScvdPrint); + expect(item.hasGuiChildren()).toBe(true); + + listSpy.mockRestore(); + printSpy.mockRestore(); + }); + + it('evaluates conditions and gui fields', async () => { + const item = new ScvdItem(undefined, '1', '1', '1'); + const condSpy = jest.spyOn(ScvdCondition.prototype, 'getResult').mockResolvedValue(true); + + await expect(item.getConditionResult()).resolves.toBe(true); + condSpy.mockRestore(); + + (item as unknown as { _property?: { getGuiValue: () => Promise } })._property = { + getGuiValue: async () => 'name' + }; + (item as unknown as { _value?: { getGuiValue: () => Promise } })._value = { + getGuiValue: async () => 'value' + }; + + await expect(item.getGuiName()).resolves.toBe('name'); + await expect(item.getGuiValue()).resolves.toBe('value'); + + (item as unknown as { _property?: undefined })._property = undefined; + (item as unknown as { _value?: undefined })._value = undefined; + await expect(item.getGuiName()).resolves.toBeUndefined(); + await expect(item.getGuiValue()).resolves.toBeUndefined(); + }); + + it('defaults condition result when no cond is set', async () => { + const item = new ScvdItem(undefined); + expect(item.hasGuiChildren()).toBe(false); + await expect(item.getConditionResult()).resolves.toBe(true); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-list-out.test.ts b/src/views/component-viewer/test/unit/model/scvd-list-out.test.ts new file mode 100644 index 00000000..b518417a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-list-out.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdListOut. + */ + +import { Json } from '../../../model/scvd-base'; +import { ScvdListOut } from '../../../model/scvd-list-out'; + +describe('ScvdListOut', () => { + it('reads XML and manages child items/lists', async () => { + const listOut = new ScvdListOut(undefined); + + expect(listOut.readXml(undefined as unknown as Json)).toBe(false); + expect(listOut.tag).toBe('XML undefined'); + + const xml: Json = { + '#Name': 'listout', + name: 'root', + start: '1', + limit: '2', + while: '3', + cond: '1', + item: { + name: 'item0', + property: 'prop', + value: 'val' + }, + list: { + name: 'child', + start: '0' + } + }; + expect(listOut.readXml(xml)).toBe(true); + expect(listOut.start).toBeDefined(); + expect(listOut.limit).toBeDefined(); + expect(listOut.while).toBeDefined(); + expect(listOut.cond).toBeDefined(); + expect(listOut.item).toHaveLength(1); + expect(listOut.list).toHaveLength(2); + + expect(listOut.listOut).toHaveLength(0); + listOut.addListOut(); + expect(listOut.listOut).toHaveLength(1); + + await expect(listOut.getGuiName()).resolves.toBeUndefined(); + }); + + it('verifies limit/while conflicts via base implementation', () => { + const listOut = new ScvdListOut(undefined); + listOut.limit = '1'; + listOut.while = '1'; + expect(listOut.verify()).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-list.test.ts b/src/views/component-viewer/test/unit/model/scvd-list.test.ts new file mode 100644 index 00000000..35c61dd7 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-list.test.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdList. + */ + +import { Json } from '../../../model/scvd-base'; +import { ScvdList } from '../../../model/scvd-list'; +import { ScvdNode } from '../../../model/scvd-node'; + +class TestParent extends ScvdNode { + constructor() { + super(undefined); + } + + public override getSymbol(name: string): ScvdNode | undefined { + if (name === 'parent') { + return this; + } + return undefined; + } +} + +describe('ScvdList', () => { + it('reads XML and populates child collections', () => { + const list = new ScvdList(undefined); + const xml: Json = { + '#Name': 'list', + name: 'root', + start: '0', + limit: '1', + cond: '1', + list: { name: 'child-list' }, + readlist: { name: 'rl', size: '1' }, + read: { name: 'r', type: 'uint8_t' }, + var: { name: 'v', type: 'uint8_t' }, + calc: { name: 'c', expression: '1' } + }; + + expect(list.readXml(xml)).toBe(true); + expect(list.start).toBeDefined(); + expect(list.limit).toBeDefined(); + expect(list.cond).toBeDefined(); + expect(list.list).toHaveLength(1); + expect(list.readList).toHaveLength(1); + expect(list.read).toHaveLength(1); + expect(list.var).toHaveLength(1); + expect(list.calc).toHaveLength(1); + expect(list.getSymbol('v')).toBe(list.var[0]); + expect(list.getSymbol('r')).toBe(list.read[0]); + expect(list.getSymbol('rl')).toBe(list.readList[0]); + }); + + it('verifies limit/while conflicts', () => { + const list = new ScvdList(undefined); + list.limit = '1'; + list.while = '1'; + expect(list.while).toBeDefined(); + expect(list.verify()).toBe(false); + + const ok = new ScvdList(undefined); + ok.limit = '1'; + expect(ok.verify()).toBe(true); + }); + + it('handles undefined XML and applyInit default', () => { + const list = new ScvdList(undefined); + expect(list.readXml(undefined as unknown as Json)).toBe(false); + expect(list.applyInit()).toBe(true); + }); + + it('handles condition evaluation branches', async () => { + const list = new ScvdList(undefined); + list.cond = '1'; + const cond = (list as unknown as { _cond?: { getResult: () => Promise } })._cond; + if (!cond) { + throw new Error('Expected condition to be set'); + } + cond.getResult = async () => false; + await expect(list.getConditionResult()).resolves.toBe(false); + + const base = new ScvdList(undefined); + await expect(base.getConditionResult()).resolves.toBe(true); + }); + + it('falls back to parent symbol resolution', () => { + const parent = new TestParent(); + const list = new ScvdList(parent); + expect(list.getSymbol('parent')).toBe(parent); + expect(list.getSymbol('missing')).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-member.test.ts b/src/views/component-viewer/test/unit/model/scvd-member.test.ts new file mode 100644 index 00000000..b3816fa7 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-member.test.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdMember. + */ + +import { Json } from '../../../model/scvd-base'; +import { ScvdMember } from '../../../model/scvd-member'; +import { ScvdExpression } from '../../../model/scvd-expression'; + +describe('ScvdMember', () => { + it('reads XML and populates member metadata', () => { + const member = new ScvdMember(undefined); + expect(member.readXml(undefined as unknown as Json)).toBe(false); + + const xml: Json = { + type: 'uint16_t', + offset: '4', + size: '2', + enum: { value: '1' } + }; + expect(member.readXml(xml)).toBe(true); + expect(member.type).toBeDefined(); + expect(member.offset).toBeDefined(); + expect(member.size).toBe(2); + expect(member.enum).toHaveLength(1); + }); + + it('computes target size and pointer behavior', () => { + const member = new ScvdMember(undefined); + member.type = 'uint32_t'; + member.size = '8'; + expect(member.getTypeSize()).toBe(4); + expect(member.getVirtualSize()).toBe(8); + expect(member.getTargetSize()).toBe(8); + expect(member.getIsPointer()).toBe(false); + + const pointer = new ScvdMember(undefined); + pointer.type = '*uint8_t'; + expect(pointer.getIsPointer()).toBe(true); + expect(pointer.getTargetSize()).toBe(4); + expect(pointer.isPointerRef()).toBe(true); + }); + + it('finds enums and members via helper APIs', async () => { + const member = new ScvdMember(undefined); + member.type = 'uint8_t'; + const enumItem = member.addEnum(); + jest.spyOn(enumItem.value, 'getValue').mockResolvedValue(3); + await expect(member.getEnum(3)).resolves.toBe(enumItem); + await expect(member.getEnum(2)).resolves.toBeUndefined(); + + const typeMember = { getMember: jest.fn().mockReturnValue(enumItem) }; + (member as unknown as { _type?: { getMember: (property: string) => unknown } })._type = typeMember; + expect(member.getMember('field')).toBe(enumItem); + }); + + it('returns member offsets and defaults', async () => { + const member = new ScvdMember(undefined); + member.offset = '10'; + const offset = (member as unknown as { _offset?: ScvdExpression })._offset; + if (!offset) { + throw new Error('Expected offset to be set'); + } + jest.spyOn(offset, 'getValue').mockResolvedValue(12); + await expect(member.getMemberOffset()).resolves.toBe(12); + + jest.spyOn(offset, 'getValue').mockResolvedValue('x'); + await expect(member.getMemberOffset()).resolves.toBe(0); + }); + + it('handles missing type information', () => { + const member = new ScvdMember(undefined); + expect(member.getMember('field')).toBeUndefined(); + expect(member.getIsPointer()).toBe(false); + expect(member.isPointerRef()).toBe(false); + expect(member.getValueType()).toBeUndefined(); + return expect(member.getMemberOffset()).resolves.toBe(0); + }); + + it('covers undefined setters and size fallback', () => { + const member = new ScvdMember(undefined); + member.type = undefined; + member.offset = undefined; + member.size = undefined; + + member.type = 'uint16_t'; + expect(member.getTargetSize()).toBe(2); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-node.test.ts b/src/views/component-viewer/test/unit/model/scvd-node.test.ts new file mode 100644 index 00000000..f069eed0 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-node.test.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdNode. + */ + +import { ScvdNode } from '../../../model/scvd-node'; +import { Json, ScvdBase } from '../../../model/scvd-base'; + +class TestNode extends ScvdNode { + constructor(parent: ScvdBase | undefined) { + super(parent); + } + + public exposeSymbolsCache(key: string, value: ScvdNode | undefined): ScvdNode | undefined { + return this.symbolsCache(key, value); + } + + public exposeClearSymbolsCache(): void { + this.clearSymbolsCache(); + } + + public exposeClearSymbolCachesRecursive(): void { + this.clearSymbolCachesRecursive(); + } +} + +class UndefinedTagNode extends TestNode { + private _overrideTag: string | undefined; + public override get tag(): string | undefined { + return this._overrideTag; + } + public override set tag(value: string | undefined) { + this._overrideTag = value; + } +} + +describe('ScvdNode', () => { + it('reads XML metadata with explicit tags', () => { + const node = new TestNode(undefined); + const xml = { __line: '10', '#Name': 'Tag', name: 'Name', info: 'Info' }; + + expect(node.readXml(xml)).toBe(true); + expect(node.lineNo).toBe('10'); + expect(node.tag).toBe('Tag'); + expect(node.name).toBe('Name'); + expect(node.info).toBe('Info'); + }); + + it('handles XML arrays and missing tags', () => { + const arrayTag = new TestNode(undefined); + expect(arrayTag.readXml([{ tag: 'item' }] as unknown as Json)).toBe(true); + expect(arrayTag.tag).toBe('item[]'); + + const arrayUnknown = new TestNode(undefined); + expect(arrayUnknown.readXml([{}] as unknown as Json)).toBe(true); + expect(arrayUnknown.tag).toBe('Array[]'); + + const defaultTag = new TestNode(undefined); + expect(defaultTag.readXml({})).toBe(true); + expect(defaultTag.tag).toBe('Internal Object'); + + const missingTag = new UndefinedTagNode(undefined); + expect(missingTag.readXml({})).toBe(true); + expect(missingTag.tag).toBe('unknown-tag'); + }); + + it('returns false when XML is undefined', () => { + const node = new TestNode(undefined); + expect(node.readXml(undefined as unknown as Json)).toBe(false); + expect(node.tag).toBe('XML undefined'); + }); + + it('delegates symbols to parent chain', () => { + const parent = new TestNode(undefined); + const child = new TestNode(parent); + const symbol = new TestNode(undefined); + jest.spyOn(parent, 'addToSymbolContext'); + + child.addToSymbolContext('name', symbol); + expect(parent.getSymbol('missing')).toBeUndefined(); + }); + + it('exposes default behaviors and GUI helpers', async () => { + const node = new TestNode(undefined); + node.name = 'Name'; + + expect(node.getMember('x')).toBeUndefined(); + expect(node.getElementRef()).toBeUndefined(); + expect(node.resolveAndLink(() => undefined)).toBe(false); + expect(node.applyInit()).toBe(true); + await expect(node.getConditionResult()).resolves.toBe(true); + await expect(node.getValue()).resolves.toBeUndefined(); + await expect(node.setValue(3)).resolves.toBe(3); + expect(node.isPointerRef()).toBe(false); + + await expect(node.getGuiName()).resolves.toBe('Name'); + await expect(node.getGuiValue()).resolves.toBeUndefined(); + expect(node.getGuiConditionResult()).toBe(true); + expect(node.getGuiLineInfo()).toContain('Tag'); + }); + + it('formats GUI values for numbers and strings', async () => { + const node = new TestNode(undefined); + node.name = 'Node'; + + (node as unknown as { getValue: () => Promise }).getValue = async () => 5; + await expect(node.getGuiValue()).resolves.toBe('5'); + + const stringNode = new TestNode(undefined); + (stringNode as unknown as { getValue: () => Promise }).getValue = async () => 'text'; + await expect(stringNode.getGuiValue()).resolves.toBe('text'); + + const otherNode = new TestNode(undefined); + (otherNode as unknown as { getValue: () => Promise }).getValue = async () => new Uint8Array([1, 2, 3]); + await expect(otherNode.getGuiValue()).resolves.toBeUndefined(); + }); + + it('caches symbols and clears recursively', () => { + const node = new TestNode(undefined); + const child = new TestNode(node); + const symbol = new TestNode(undefined); + + expect(node.exposeSymbolsCache('k', symbol)).toBe(symbol); + expect(node.exposeSymbolsCache('k', undefined)).toBe(symbol); + + node.exposeClearSymbolCachesRecursive(); + expect(node.exposeSymbolsCache('k', undefined)).toBeUndefined(); + expect(child.exposeSymbolsCache('k', undefined)).toBeUndefined(); + }); + + it('logs default errors for unimplemented accessors', async () => { + const node = new TestNode(undefined); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + expect(node.writeAt(0, 8, 1)).toBeUndefined(); + expect(node.readAt(0, 8)).toBeUndefined(); + expect(node.getTargetSize()).toBeUndefined(); + expect(node.getTypeSize()).toBeUndefined(); + expect(node.getVirtualSize()).toBeUndefined(); + expect(node.getIsPointer()).toBe(false); + await expect(node.getArraySize()).resolves.toBeUndefined(); + await expect(node.getMemberOffset()).resolves.toBeUndefined(); + expect(node.getElementBitWidth()).toBeUndefined(); + expect(node.getValueType()).toBeUndefined(); + + errorSpy.mockRestore(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-object.test.ts b/src/views/component-viewer/test/unit/model/scvd-object.test.ts new file mode 100644 index 00000000..39ff0c2a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-object.test.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdObject(s). + */ + +import { Json } from '../../../model/scvd-base'; +import { ScvdObject, ScvdObjects } from '../../../model/scvd-object'; +import { ScvdVar } from '../../../model/scvd-var'; + +describe('ScvdObject', () => { + it('reads objects and returns undefined for global symbols', () => { + const objects = new ScvdObjects(undefined); + expect(objects.readXml(undefined as unknown as Json)).toBe(false); + + const xml: Json = { + object: { + name: 'obj', + var: { name: 'v', type: 'uint8_t' } + } + }; + expect(objects.readXml(xml)).toBe(true); + expect(objects.objects).toHaveLength(1); + expect(objects.getSymbol('anything')).toBeUndefined(); + }); + + it('reads object children and manages symbol context', () => { + const object = new ScvdObject(undefined); + const xml: Json = { + name: 'obj', + var: { name: 'v', type: 'uint8_t' }, + read: { name: 'r', type: 'uint8_t' }, + readlist: { name: 'rl', type: 'uint8_t' }, + list: { name: 'list' }, + calc: { name: 'calc', expression: '1' }, + out: { name: 'out' } + }; + + expect(object.readXml(xml)).toBe(true); + expect(object.readXml(undefined as unknown as Json)).toBe(false); + expect(object.var).toHaveLength(1); + expect(object.read).toHaveLength(1); + expect(object.readList).toHaveLength(1); + expect(object.list).toHaveLength(1); + expect(object.out).toHaveLength(1); + expect(object.vars).toHaveLength(1); + expect(object.calcs).toHaveLength(1); + + const firstVar = object.var[0]; + expect(object.getSymbol('v')).toBe(firstVar); + expect(object.getVar('v')).toBe(firstVar); + expect(object.getVar('missing')).toBeUndefined(); + expect(object.getRead('r')).toBe(object.read[0]); + expect(object.getRead('missing')).toBeUndefined(); + }); + + it('does not overwrite existing symbol context entries', () => { + const object = new ScvdObject(undefined); + const varItem = new ScvdVar(object); + varItem.name = 'dup'; + object.addToSymbolContext('dup', varItem); + + const otherVar = new ScvdVar(object); + otherVar.name = 'dup'; + object.addToSymbolContext('dup', otherVar); + + expect(object.symbolContext.get('dup')).toBe(varItem); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-out.test.ts b/src/views/component-viewer/test/unit/model/scvd-out.test.ts new file mode 100644 index 00000000..d86b9052 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-out.test.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdOut. + */ + +import { Json } from '../../../model/scvd-base'; +import { ScvdOut } from '../../../model/scvd-out'; +import { ScvdCondition } from '../../../model/scvd-condition'; + +describe('ScvdOut', () => { + it('reads XML and manages child collections', () => { + const out = new ScvdOut(undefined); + expect(out.readXml(undefined as unknown as Json)).toBe(false); + + const xml: Json = { + value: '1', + type: 'uint8_t', + cond: '1', + item: { name: 'item', value: '2' }, + list: { name: 'list', value: '3' } + }; + expect(out.readXml(xml)).toBe(true); + expect(out.value).toBeDefined(); + expect(out.type).toBeDefined(); + expect(out.cond).toBeDefined(); + expect(out.item).toHaveLength(1); + expect(out.list).toHaveLength(1); + expect(out.getValueType()).toBe('uint8_t'); + + out.addItem(); + out.addList(); + expect(out.item.length).toBe(2); + expect(out.list.length).toBe(2); + + out.type = 'uint16_t'; + expect(out.getValueType()).toBe('uint16_t'); + }); + + it('handles condition evaluation branches', async () => { + const out = new ScvdOut(undefined); + await expect(out.getConditionResult()).resolves.toBe(true); + + out.cond = '1'; + const cond = (out as unknown as { _cond?: ScvdCondition })._cond; + if (!cond) { + throw new Error('Expected condition to be set'); + } + cond.getResult = async () => false; + await expect(out.getConditionResult()).resolves.toBe(false); + }); + + it('ignores undefined setter values', () => { + const out = new ScvdOut(undefined); + out.value = undefined; + out.type = undefined; + out.cond = undefined; + expect(out.value).toBeUndefined(); + expect(out.type).toBeUndefined(); + expect(out.cond).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-print-expression.test.ts b/src/views/component-viewer/test/unit/model/scvd-print-expression.test.ts new file mode 100644 index 00000000..9d38d10d --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-print-expression.test.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdPrintExpression. + */ + +import { ScvdPrintExpression } from '../../../model/scvd-print-expression'; + +describe('ScvdPrintExpression', () => { + it('constructs and defers configure/validate to base', () => { + const expr = new ScvdPrintExpression(undefined, '1+2', 'value'); + expect(expr.configure()).toBe(true); + expect(expr.validate(true)).toBe(true); + }); + + it('delegates GUI helpers to base', async () => { + const expr = new ScvdPrintExpression(undefined, '1', 'value'); + expr.name = 'Name'; + await expect(expr.getGuiName()).resolves.toBe('Name'); + await expect(expr.getGuiValue()).resolves.toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-print.test.ts b/src/views/component-viewer/test/unit/model/scvd-print.test.ts new file mode 100644 index 00000000..589803b6 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-print.test.ts @@ -0,0 +1,100 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdPrint. + */ + +import { ScvdCondition } from '../../../model/scvd-condition'; +import { ScvdPrint } from '../../../model/scvd-print'; +import { ScvdValueOutput } from '../../../model/scvd-value-output'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdPrint', () => { + it('returns false when XML is undefined', () => { + const print = new ScvdPrint(undefined); + expect(print.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads properties and conditions from XML', () => { + const print = new ScvdPrint(undefined); + const xml = { + cond: '1', + property: 'prop', + value: 'val', + bold: '1', + alert: '0' + }; + + expect(print.readXml(xml)).toBe(true); + expect(print.cond).toBeInstanceOf(ScvdCondition); + expect(print.property).toBeInstanceOf(ScvdValueOutput); + expect(print.value).toBeInstanceOf(ScvdValueOutput); + expect(print.bold).toBeInstanceOf(ScvdCondition); + expect(print.alert).toBeInstanceOf(ScvdCondition); + }); + + it('uses conditions when present', async () => { + const print = new ScvdPrint(undefined); + print.cond = '1'; + const condSpy = jest.spyOn(ScvdCondition.prototype, 'getResult').mockResolvedValue(false); + + await expect(print.getConditionResult()).resolves.toBe(false); + + condSpy.mockRestore(); + }); + + it('returns default condition result when no condition is set', async () => { + const print = new ScvdPrint(undefined); + await expect(print.getConditionResult()).resolves.toBe(true); + }); + + it('returns GUI name/value from value outputs', async () => { + const print = new ScvdPrint(undefined); + + await expect(print.getGuiName()).resolves.toBeUndefined(); + await expect(print.getGuiValue()).resolves.toBeUndefined(); + + const nameSpy = jest.spyOn(ScvdValueOutput.prototype, 'getGuiName').mockResolvedValue('NAME'); + const valueSpy = jest.spyOn(ScvdValueOutput.prototype, 'getGuiValue').mockResolvedValue('VALUE'); + + print.property = 'prop'; + print.value = 'val'; + + await expect(print.getGuiName()).resolves.toBe('NAME'); + await expect(print.getGuiValue()).resolves.toBe('VALUE'); + + nameSpy.mockRestore(); + valueSpy.mockRestore(); + }); + + it('ignores undefined setter inputs', () => { + const print = new ScvdPrint(undefined); + print.property = undefined; + print.value = undefined; + print.cond = undefined; + print.bold = undefined; + print.alert = undefined; + + expect(print.property).toBeUndefined(); + expect(print.value).toBeUndefined(); + expect(print.cond).toBeUndefined(); + expect(print.bold).toBeUndefined(); + expect(print.alert).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-read.test.ts b/src/views/component-viewer/test/unit/model/scvd-read.test.ts new file mode 100644 index 00000000..db91899a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-read.test.ts @@ -0,0 +1,140 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdRead. + */ + +import { ScvdCondition } from '../../../model/scvd-condition'; +import { ScvdEndian } from '../../../model/scvd-endian'; +import { ScvdRead } from '../../../model/scvd-read'; +import { ScvdSymbol } from '../../../model/scvd-symbol'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdRead', () => { + it('returns false when XML is undefined', () => { + const read = new ScvdRead(undefined); + expect(read.readXml(undefined as unknown as Json)).toBe(false); + expect(read.tag).toBe('XML undefined'); + }); + + it('reads XML fields into typed members', () => { + const read = new ScvdRead(undefined); + const condSpy = jest.spyOn(ScvdCondition.prototype, 'getResult').mockResolvedValue(false); + + const xml = { + type: 'uint32_t', + symbol: 'sym', + offset: '4', + const: '1', + cond: '0', + size: '2', + endian: 'B' + }; + + expect(read.readXml(xml)).toBe(true); + expect(read.type).toBeDefined(); + expect(read.symbol).toBeInstanceOf(ScvdSymbol); + expect(read.offset).toBeDefined(); + expect(read.const).toBe(true); + expect(read.cond).toBeInstanceOf(ScvdCondition); + expect(read.size).toBeDefined(); + expect(read.endian).toBeInstanceOf(ScvdEndian); + + read.endian = 'L'; + expect(read.endian?.endian).toBe('L'); + + read.symbol = 'again'; + expect(read.symbol?.symbol).toBe('sym'); + + return read.getConditionResult().then(result => { + expect(result).toBe(false); + condSpy.mockRestore(); + }); + }); + + it('handles array size values and pointer flag', async () => { + const read = new ScvdRead(undefined); + + (read as unknown as { _size?: { getValue: () => Promise } })._size = { + getValue: async () => 7n + }; + await expect(read.getArraySize()).resolves.toBe(7); + + (read as unknown as { _size?: { getValue: () => Promise } })._size = { + getValue: async () => 3 + }; + await expect(read.getArraySize()).resolves.toBe(3); + + (read as unknown as { _size?: { getValue: () => Promise } })._size = { + getValue: async () => 'bad' + }; + await expect(read.getArraySize()).resolves.toBeUndefined(); + + (read as unknown as { _type?: { getIsPointer: () => boolean } })._type = { + getIsPointer: () => true + }; + expect(read.getIsPointer()).toBe(true); + }); + + it('ignores undefined setters for optional fields', () => { + const read = new ScvdRead(undefined); + read.type = undefined; + read.symbol = undefined; + read.offset = undefined; + read.const = undefined; + read.cond = undefined; + read.size = undefined; + read.endian = undefined; + + expect(read.type).toBeUndefined(); + expect(read.symbol).toBeUndefined(); + expect(read.offset).toBeUndefined(); + expect(read.cond).toBeUndefined(); + expect(read.size).toBeUndefined(); + expect(read.endian).toBeUndefined(); + }); + + it('parses const flags and default pointer state', () => { + const read = new ScvdRead(undefined); + read.const = '0'; + expect(read.const).toBe(false); + expect(read.getIsPointer()).toBe(false); + }); + + it('falls back to default condition result when no cond is set', async () => { + const read = new ScvdRead(undefined); + await expect(read.getConditionResult()).resolves.toBe(true); + }); + + it('delegates size and member lookups to the type', () => { + const read = new ScvdRead(undefined); + const member = new ScvdRead(read); + (read as unknown as { _type?: { getTypeSize: () => number; getVirtualSize: () => number; getMember: (n: string) => ScvdRead; getValueType: () => string } })._type = { + getTypeSize: () => 4, + getVirtualSize: () => 8, + getMember: () => member, + getValueType: () => 'uint32' + }; + + expect(read.getTargetSize()).toBe(4); + expect(read.getVirtualSize()).toBe(8); + expect(read.getMember('m')).toBe(member); + expect(read.getValueType()).toBe('uint32'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-readlist.test.ts b/src/views/component-viewer/test/unit/model/scvd-readlist.test.ts new file mode 100644 index 00000000..826b39bf --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-readlist.test.ts @@ -0,0 +1,206 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdReadList. + */ + +import { ScvdExpression } from '../../../model/scvd-expression'; +import { ScvdReadList } from '../../../model/scvd-readlist'; +import { ScvdNode } from '../../../model/scvd-node'; +import { Json } from '../../../model/scvd-base'; +import { ResolveSymbolCb, ResolveType } from '../../../resolver'; + +const makeType = (overrides?: Partial<{ size: number; vsize: number; isPointer: boolean }>) => ({ + getTypeSize: () => overrides?.size ?? 4, + getVirtualSize: () => overrides?.vsize ?? 8, + getIsPointer: () => overrides?.isPointer ?? false +}); + +describe('ScvdReadList', () => { + it('returns false when XML is undefined', () => { + const readlist = new ScvdReadList(undefined); + expect(readlist.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads attributes from XML', () => { + const readlist = new ScvdReadList(undefined); + const xml = { + count: '2', + next: 'next', + init: '1', + based: '1' + }; + + expect(readlist.readXml(xml)).toBe(true); + expect(readlist.count).toBeInstanceOf(ScvdExpression); + expect(readlist.next).toBe('next'); + expect(readlist.init).toBe(1); + expect(readlist.based).toBe(1); + }); + + it('computes target, virtual sizes, and pointer behavior', () => { + const readlist = new ScvdReadList(undefined); + (readlist as unknown as { _type?: unknown })._type = makeType({ size: 6, vsize: 10, isPointer: false }); + + expect(readlist.getTargetSize()).toBe(6); + expect(readlist.getVirtualSize()).toBe(10); + expect(readlist.getIsPointer()).toBe(false); + + readlist.based = 1; + expect(readlist.getIsPointer()).toBe(true); + }); + + it('treats type pointers as pointers when based is false', () => { + const readlist = new ScvdReadList(undefined); + (readlist as unknown as { _type?: unknown })._type = makeType({ isPointer: true }); + + expect(readlist.getIsPointer()).toBe(true); + }); + + it('treats missing types as non-pointers and ignores undefined setters', () => { + const readlist = new ScvdReadList(undefined); + readlist.count = '1'; + readlist.init = '1'; + readlist.based = '1'; + + readlist.count = undefined; + readlist.init = undefined; + readlist.based = undefined; + + expect(readlist.getIsPointer()).toBe(true); + expect(readlist.count).toBeInstanceOf(ScvdExpression); + expect(readlist.init).toBe(1); + expect(readlist.based).toBe(1); + + const readlistNoType = new ScvdReadList(undefined); + expect(readlistNoType.getIsPointer()).toBe(false); + }); + + it('handles counts including min/max clamps', async () => { + const readlist = new ScvdReadList(undefined); + readlist.count = 'count'; + + const expr = readlist.count as ScvdExpression; + const valueSpy = jest.spyOn(expr, 'getValue'); + + valueSpy.mockResolvedValueOnce(undefined); + await expect(readlist.getCount()).resolves.toBeUndefined(); + + valueSpy.mockResolvedValueOnce(NaN); + await expect(readlist.getCount()).resolves.toBeUndefined(); + + valueSpy.mockResolvedValueOnce(0); + await expect(readlist.getCount()).resolves.toBe(ScvdReadList.READ_SIZE_MIN); + + valueSpy.mockResolvedValueOnce(ScvdReadList.READ_SIZE_MAX + 1); + await expect(readlist.getCount()).resolves.toBe(ScvdReadList.READ_SIZE_MAX); + + valueSpy.mockResolvedValueOnce(5); + await expect(readlist.getCount()).resolves.toBe(5); + + valueSpy.mockRestore(); + }); + + it('returns undefined when no count expression is defined', async () => { + const readlist = new ScvdReadList(undefined); + await expect(readlist.getCount()).resolves.toBeUndefined(); + }); + + it('exposes next and init getters', () => { + const readlist = new ScvdReadList(undefined); + readlist.next = 'next'; + readlist.init = '1'; + + expect(readlist.getNext()).toBe('next'); + expect(readlist.getInit()).toBe(1); + }); + + it('logs when next member is missing during resolve', () => { + const readlist = new ScvdReadList(undefined); + readlist.next = 'field'; + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const resolveFunc = jest.fn, Parameters>((name, resolveType) => { + if (resolveType === ResolveType.localType) { + return { name } as unknown as ScvdNode; + } + return undefined; + }); + + expect(readlist.resolveAndLink(resolveFunc)).toBe(false); + expect(errorSpy).toHaveBeenCalledTimes(1); + + errorSpy.mockRestore(); + }); + + it('does not log when next member resolves', () => { + const readlist = new ScvdReadList(undefined); + readlist.next = 'field'; + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const resolveFunc = jest.fn, Parameters>((name, resolveType, scvdObject) => { + if (resolveType === ResolveType.localType) { + return { name } as unknown as ScvdNode; + } + if (resolveType === ResolveType.localMember && scvdObject) { + return { name } as unknown as ScvdNode; + } + return undefined; + }); + + expect(readlist.resolveAndLink(resolveFunc)).toBe(false); + expect(errorSpy).not.toHaveBeenCalled(); + + errorSpy.mockRestore(); + }); + + it('skips resolve checks when next is undefined', () => { + const readlist = new ScvdReadList(undefined); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const resolveFunc = jest.fn, Parameters>(() => undefined); + + expect(readlist.resolveAndLink(resolveFunc)).toBe(false); + expect(resolveFunc).not.toHaveBeenCalled(); + expect(errorSpy).not.toHaveBeenCalled(); + + errorSpy.mockRestore(); + }); + + it('skips logging when no typedef is resolved', () => { + const readlist = new ScvdReadList(undefined); + readlist.next = 'field'; + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const resolveFunc = jest.fn, Parameters>(() => undefined); + + expect(readlist.resolveAndLink(resolveFunc)).toBe(false); + expect(errorSpy).not.toHaveBeenCalled(); + + errorSpy.mockRestore(); + }); + + it('returns true from applyInit regardless of init value', () => { + const readlist = new ScvdReadList(undefined); + readlist.init = '1'; + expect(readlist.applyInit()).toBe(true); + + readlist.init = '0'; + expect(readlist.applyInit()).toBe(true); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-symbol.test.ts b/src/views/component-viewer/test/unit/model/scvd-symbol.test.ts new file mode 100644 index 00000000..5db89922 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-symbol.test.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdSymbol. + */ + +import { ScvdSymbol } from '../../../model/scvd-symbol'; +import { ExecutionContext } from '../../../scvd-eval-context'; + +describe('ScvdSymbol', () => { + it('tracks symbol and address values', () => { + const symbol = new ScvdSymbol(undefined, 'SYMBOL'); + expect(symbol.symbol).toBe('SYMBOL'); + + symbol.symbol = 'NEW'; + expect(symbol.symbol).toBe('NEW'); + + expect(symbol.address).toBeUndefined(); + symbol.address = 1234; + expect(symbol.address).toBe(1234); + }); + + it('returns undefined offsets for missing names', () => { + const symbol = new ScvdSymbol(undefined, 'SYMBOL'); + expect(symbol.getOffset(undefined)).toBeUndefined(); + expect(symbol.getOffset('missing')).toBeUndefined(); + }); + + it('fetches symbol information when context and symbol are available', async () => { + const symbol = new ScvdSymbol(undefined, 'sym'); + const getSymbolInfo = jest.fn().mockResolvedValue({ + name: 'sym', + address: 256, + member: [ + { name: 'field', size: 4, offset: 8 }, + { name: 'next', size: 2, offset: 12 } + ] + }); + const context = { debugTarget: { getSymbolInfo } } as unknown as ExecutionContext; + + symbol.setExecutionContext(context); + const result = await symbol.fetchSymbolInformation(); + + expect(result).toBe(true); + expect(symbol.address).toBe(256); + expect(symbol.memberInfo).toHaveLength(2); + expect(symbol.getOffset('field')).toBe(8); + expect(symbol.getOffset('next')).toBe(12); + }); + + it('returns false without symbol or execution context', async () => { + const symbol = new ScvdSymbol(undefined, 'sym'); + symbol.symbol = undefined; + await expect(symbol.fetchSymbolInformation()).resolves.toBe(false); + + const withSymbol = new ScvdSymbol(undefined, 'sym'); + await expect(withSymbol.fetchSymbolInformation()).resolves.toBe(false); + }); + + it('handles missing symbol info gracefully', async () => { + const symbol = new ScvdSymbol(undefined, 'sym'); + const getSymbolInfo = jest.fn().mockResolvedValue(undefined); + const context = { debugTarget: { getSymbolInfo } } as unknown as ExecutionContext; + + symbol.setExecutionContext(context); + const result = await symbol.fetchSymbolInformation(); + expect(result).toBe(true); + expect(symbol.address).toBeUndefined(); + expect(symbol.memberInfo).toHaveLength(0); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-template.test.ts b/src/views/component-viewer/test/unit/model/scvd-template.test.ts new file mode 100644 index 00000000..a575e0a2 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-template.test.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdTemplate. + */ + +import { ScvdTemplate } from '../../../model/scvd-template'; + +describe('ScvdTemplate', () => { + it('constructs with an optional parent', () => { + const template = new ScvdTemplate(undefined); + expect(template.parent).toBeUndefined(); + }); + + it('reports classname', () => { + const template = new ScvdTemplate(undefined); + expect(template.classname).toBe('ScvdTemplate'); + }); + + it('delegates readXml when xml is undefined', () => { + const template = new ScvdTemplate(undefined); + expect(template.readXml(undefined as unknown as Record)).toBe(false); + expect(template.tag).toBe('XML undefined'); + }); + + it('reads tag, name, info, and line from xml', () => { + const template = new ScvdTemplate(undefined); + const xml = { + '#Name': 'template', + name: 'MyTemplate', + info: 'Template info', + __line: '42', + }; + expect(template.readXml(xml)).toBe(true); + expect(template.tag).toBe('template'); + expect(template.name).toBe('MyTemplate'); + expect(template.info).toBe('Template info'); + expect(template.lineNo).toBe('42'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-typedef.test.ts b/src/views/component-viewer/test/unit/model/scvd-typedef.test.ts new file mode 100644 index 00000000..f62f3e4a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-typedef.test.ts @@ -0,0 +1,298 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdTypedefs/ScvdTypedef. + */ + +import { ScvdExpression } from '../../../model/scvd-expression'; +import { ScvdMember } from '../../../model/scvd-member'; +import { ScvdSymbol } from '../../../model/scvd-symbol'; +import { ScvdTypedef, ScvdTypedefs } from '../../../model/scvd-typedef'; +import { ScvdVar } from '../../../model/scvd-var'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdTypedefs', () => { + it('returns false when XML is undefined', () => { + const typedefs = new ScvdTypedefs(undefined); + expect(typedefs.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads typedef entries from XML', () => { + const typedefs = new ScvdTypedefs(undefined); + const readSpy = jest.spyOn(ScvdTypedef.prototype, 'readXml').mockReturnValue(true); + + const xml = { + typedef: [{ name: 'A' }, { name: 'B' }] + }; + + expect(typedefs.readXml(xml)).toBe(true); + expect(typedefs.typedef).toHaveLength(2); + + readSpy.mockRestore(); + }); + + it('calculates typedefs when present', async () => { + const typedefs = new ScvdTypedefs(undefined); + const typedef = typedefs.addTypedef(); + const calcSpy = jest.spyOn(typedef, 'calculateTypedef').mockResolvedValue(); + + await typedefs.calculateTypedefs(); + expect(calcSpy).toHaveBeenCalledTimes(1); + }); + + it('skips calculation when no typedefs are defined', async () => { + const typedefs = new ScvdTypedefs(undefined); + await expect(typedefs.calculateTypedefs()).resolves.toBeUndefined(); + }); +}); + +describe('ScvdTypedef', () => { + it('returns false when XML is undefined', () => { + const typedef = new ScvdTypedef(undefined); + expect(typedef.readXml(undefined as unknown as Json)).toBe(false); + }); + + it('reads members and vars from XML', () => { + const typedef = new ScvdTypedef(undefined); + const memberSpy = jest.spyOn(ScvdMember.prototype, 'readXml').mockReturnValue(true); + const varSpy = jest.spyOn(ScvdVar.prototype, 'readXml').mockReturnValue(true); + + const xml = { + size: '4', + import: 'SYM', + member: [{ name: 'm1', __line: '2' }, { name: 'm2', __line: '1' }], + var: [{ name: 'v1', __line: '3' }] + }; + + expect(typedef.readXml(xml)).toBe(true); + expect(typedef.size).toBeInstanceOf(ScvdExpression); + expect(typedef.import).toBeInstanceOf(ScvdSymbol); + expect(typedef.member).toHaveLength(2); + expect(typedef.var).toHaveLength(1); + + memberSpy.mockRestore(); + varSpy.mockRestore(); + }); + + it('returns type sizing and pointer defaults', () => { + const typedef = new ScvdTypedef(undefined); + (typedef as unknown as { _targetSize?: number })._targetSize = 12; + + expect(typedef.getTypeSize()).toBe(12); + expect(typedef.getVirtualSize()).toBe(12); + expect(typedef.getIsPointer()).toBe(false); + }); + + it('ignores undefined setter inputs', () => { + const typedef = new ScvdTypedef(undefined); + typedef.size = undefined; + typedef.import = undefined; + + expect(typedef.size).toBeUndefined(); + expect(typedef.import).toBeUndefined(); + }); + + it('resolves members and vars by name', () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + const variable = typedef.addVar(); + + member.name = 'field'; + variable.name = 'local'; + + expect(typedef.getMember('field')).toBe(member); + expect(typedef.getMember('local')).toBe(variable); + }); + + it('calculates offsets with explicit member offsets and size overflow', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + + member.name = 'm1'; + member.offset = '8'; + jest.spyOn(member.offset as ScvdExpression, 'getValue').mockResolvedValue(8); + jest.spyOn(member, 'getTypeSize').mockReturnValue(4); + + typedef.size = '8'; + jest.spyOn(typedef.size as ScvdExpression, 'getValue').mockResolvedValue(8); + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + await typedef.calculateOffsets(); + errorSpy.mockRestore(); + + expect(typedef.getTargetSize()).toBe(8); + expect(typedef.getVirtualSize()).toBe(16); + }); + + it('pads typedef size when offsets are smaller than size', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + + member.name = 'm1'; + member.offset = '0'; + jest.spyOn(member.offset as ScvdExpression, 'getValue').mockResolvedValue(0); + jest.spyOn(member, 'getTypeSize').mockReturnValue(1); + + typedef.size = '4'; + jest.spyOn(typedef.size as ScvdExpression, 'getValue').mockResolvedValue(4); + + await typedef.calculateOffsets(); + + expect(typedef.getTargetSize()).toBe(4); + expect(typedef.getVirtualSize()).toBe(8); + }); + + it('pads typedef size when no members are present', async () => { + const typedef = new ScvdTypedef(undefined); + typedef.size = '4'; + jest.spyOn(typedef.size as ScvdExpression, 'getValue').mockResolvedValue(4); + + await typedef.calculateOffsets(); + + expect(typedef.getTargetSize()).toBe(4); + expect(typedef.getVirtualSize()).toBe(8); + }); + + it('keeps size when offsets match the declared size', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + + member.name = 'm1'; + member.offset = '0'; + jest.spyOn(member.offset as ScvdExpression, 'getValue').mockResolvedValue(0); + jest.spyOn(member, 'getTypeSize').mockReturnValue(4); + + typedef.size = '4'; + jest.spyOn(typedef.size as ScvdExpression, 'getValue').mockResolvedValue(4); + + await typedef.calculateOffsets(); + + expect(typedef.getTargetSize()).toBe(4); + expect(typedef.getVirtualSize()).toBe(8); + }); + + it('calculates offsets without import and assigns defaults', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + + member.name = 'm1'; + jest.spyOn(member, 'getTypeSize').mockReturnValue(2); + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + await typedef.calculateOffsets(); + errorSpy.mockRestore(); + + expect(member.offset?.expression).toBe('0'); + expect(typedef.getTargetSize()).toBe(2); + expect(typedef.getVirtualSize()).toBe(8); + }); + + it('calculates offsets with import and virtual size expansion', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + const variable = typedef.addVar(); + + member.name = 'm1'; + typedef.import = 'SYM'; + const symbol = typedef.import as ScvdSymbol; + symbol.memberInfo.push({ name: 'm1', size: 1, offset: 4 }); + + jest.spyOn(member, 'getTypeSize').mockReturnValue(1); + jest.spyOn(variable, 'getTargetSize').mockReturnValue(2); + + typedef.size = '8'; + jest.spyOn(typedef.size as ScvdExpression, 'getValue').mockResolvedValue(8); + + await typedef.calculateOffsets(); + + expect(member.offset?.expression).toBe('4'); + expect(variable.offset?.expression).toBe('12'); + expect(typedef.getTargetSize()).toBe(8); + expect(typedef.getVirtualSize()).toBe(14); + }); + + it('handles missing import offsets and undefined member sizes', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + const variable = typedef.addVar(); + + member.name = 'm1'; + typedef.import = 'SYM'; + jest.spyOn(member, 'getTypeSize').mockReturnValue(undefined); + jest.spyOn(variable, 'getTargetSize').mockReturnValue(undefined); + + await typedef.calculateOffsets(); + + expect(member.offset).toBeUndefined(); + expect(variable.offset?.expression).toBe('4'); + expect(typedef.getTargetSize()).toBe(0); + expect(typedef.getVirtualSize()).toBe(8); + }); + + it('skips offset updates when offset values are undefined', async () => { + const typedef = new ScvdTypedef(undefined); + const member = typedef.addMember(); + + member.name = 'm1'; + member.offset = '1'; + jest.spyOn(member.offset as ScvdExpression, 'getValue').mockResolvedValue(undefined); + jest.spyOn(member, 'getTypeSize').mockReturnValue(1); + + await typedef.calculateOffsets(); + + expect(typedef.getTargetSize()).toBe(1); + }); + + it('invokes symbol fetch when calculating typedefs', async () => { + const typedef = new ScvdTypedef(undefined); + typedef.import = 'SYM'; + + let fetchComplete = false; + const fetchSpy = jest.spyOn(typedef.import as ScvdSymbol, 'fetchSymbolInformation').mockImplementation(async () => { + await Promise.resolve(); + fetchComplete = true; + return true; + }); + const offsetsSpy = jest.spyOn(typedef, 'calculateOffsets').mockImplementation(async () => { + expect(fetchComplete).toBe(true); + }); + + await typedef.calculateTypedef(); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(offsetsSpy).toHaveBeenCalledTimes(1); + }); + + it('calculates typedefs without import symbols', async () => { + const typedef = new ScvdTypedef(undefined); + const offsetsSpy = jest.spyOn(typedef, 'calculateOffsets').mockResolvedValue(); + + await typedef.calculateTypedef(); + expect(offsetsSpy).toHaveBeenCalledTimes(1); + }); + + it('updates import name when import already exists', () => { + const typedef = new ScvdTypedef(undefined); + typedef.import = 'SYM'; + const existing = typedef.import as ScvdSymbol; + + typedef.import = 'NEXT'; + expect(existing.name).toBe('NEXT'); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-utils.test.ts b/src/views/component-viewer/test/unit/model/scvd-utils.test.ts new file mode 100644 index 00000000..99535932 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-utils.test.ts @@ -0,0 +1,90 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for scvd-utils. + */ + +import { + AddText, + clearSignBit, + getArrayFromJson, + getLineNumberFromJson, + getObjectFromJson, + getStringField, + getStringFromJson, + getTextBodyFromJson, + insertString, + unsignedRShift +} from '../../../model/scvd-utils'; + +import { Json } from '../../../model/scvd-base'; + +describe('scvd-utils', () => { + it('adds text with string and array inputs', () => { + expect(AddText('', 'a')).toBe('a'); + expect(AddText('a', 'b')).toBe('a b'); + expect(AddText('', ['a', 'b'])).toBe('a b'); + expect(AddText('a', ['b', 'c'])).toBe('a b c'); + }); + + it('inserts strings at requested positions', () => { + expect(insertString('abc', 'X', 0)).toBe('Xabc'); + expect(insertString('abc', 'X', 10)).toBe('abcX'); + expect(insertString('abcd', 'X', 2)).toBe('abXcd'); + }); + + it('handles bit operations', () => { + expect(unsignedRShift(-1, 1)).toBe(0x7fffffff); + expect(clearSignBit(-1)).toBe(0xffffffff); + }); + + it('parses objects and strings from JSON', () => { + expect(getObjectFromJson(undefined)).toBeUndefined(); + expect(getObjectFromJson(null)).toBeUndefined(); + expect(getObjectFromJson('x')).toBeUndefined(); + const obj = { key: 'value' }; + expect(getObjectFromJson(obj)).toBe(obj); + + expect(getStringFromJson('x')).toBe('x'); + expect(getStringFromJson(1)).toBeUndefined(); + + expect(getStringField(undefined, 'key')).toBeUndefined(); + expect(getStringField({ key: 'value' }, 'key')).toBe('value'); + expect(getStringField({ key: 1 }, 'key')).toBeUndefined(); + }); + + it('returns arrays from JSON values', () => { + expect(getArrayFromJson(undefined)).toBeUndefined(); + expect(getArrayFromJson([1, 2])).toEqual([1, 2]); + expect(getArrayFromJson('x')).toEqual(['x']); + }); + + it('extracts text bodies', () => { + expect(getTextBodyFromJson('a;b' as unknown as Json)).toEqual(['a', 'b']); + expect(getTextBodyFromJson({ '#text': 'a\n b ' })).toEqual(['a', 'b']); + expect(getTextBodyFromJson({ _: 'a;\n\r' })).toEqual(['a']); + expect(getTextBodyFromJson({ text: 'a; b; ' })).toEqual(['a', 'b']); + expect(getTextBodyFromJson({} as Json)).toBeUndefined(); + }); + + it('reads line numbers from json', () => { + expect(getLineNumberFromJson({ __line: '12' })).toBe('12'); + expect(getLineNumberFromJson({ __line: 12 } as unknown as Json)).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-value-output.test.ts b/src/views/component-viewer/test/unit/model/scvd-value-output.test.ts new file mode 100644 index 00000000..3146936a --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-value-output.test.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdValueOutput. + */ + +import { ScvdPrintExpression } from '../../../model/scvd-print-expression'; +import { ScvdValueOutput } from '../../../model/scvd-value-output'; + +describe('ScvdValueOutput', () => { + it('creates expressions and updates them', () => { + const output = new ScvdValueOutput(undefined, 'A', 'value'); + expect(output.expression).toBeInstanceOf(ScvdPrintExpression); + + const original = output.expression; + output.expression = 'B'; + expect(output.expression).toBe(original); + }); + + it('creates a new expression when missing', () => { + const output = new ScvdValueOutput(undefined, 'A', 'value'); + const outputState = output as unknown as { _expression?: ScvdPrintExpression }; + delete outputState._expression; + output.expression = 'B'; + expect(output.expression).toBeInstanceOf(ScvdPrintExpression); + }); + + it('returns GUI name/value from expression results', async () => { + const output = new ScvdValueOutput(undefined, 'A', 'value'); + const resultSpy = jest.spyOn(ScvdPrintExpression.prototype, 'getResultString').mockResolvedValue('OK'); + + await expect(output.getGuiName()).resolves.toBe('OK'); + await expect(output.getGuiValue()).resolves.toBe('OK'); + + resultSpy.mockRestore(); + }); + + it('returns undefined when expression is missing', async () => { + const output = new ScvdValueOutput(undefined, 'A', 'value'); + const outputState = output as unknown as { _expression?: ScvdPrintExpression }; + delete outputState._expression; + + await expect(output.getGuiName()).resolves.toBeUndefined(); + await expect(output.getGuiValue()).resolves.toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/model/scvd-var.test.ts b/src/views/component-viewer/test/unit/model/scvd-var.test.ts new file mode 100644 index 00000000..aa6bab88 --- /dev/null +++ b/src/views/component-viewer/test/unit/model/scvd-var.test.ts @@ -0,0 +1,125 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdVar. + */ + +import { ScvdVar } from '../../../model/scvd-var'; +import { Json } from '../../../model/scvd-base'; + +describe('ScvdVar', () => { + it('returns false when XML is undefined', () => { + const item = new ScvdVar(undefined); + expect(item.readXml(undefined as unknown as Json)).toBe(false); + expect(item.tag).toBe('XML undefined'); + }); + + it('reads XML and exposes type/size fields', () => { + const item = new ScvdVar(undefined); + expect(item.readXml({ value: '1', type: 'uint32_t', size: '2' })).toBe(true); + expect(item.value).toBeDefined(); + expect(item.type).toBeDefined(); + expect(item.size).toBe(2); + + item.size = undefined; + expect(item.size).toBe(2); + + const prevValue = item.value; + item.value = undefined; + expect(item.value).toBe(prevValue); + }); + + it('reads value and size from injected expression', async () => { + const item = new ScvdVar(undefined); + await expect(item.getValue()).resolves.toBeUndefined(); + + (item as unknown as { _value?: { getValue: () => Promise } })._value = { + getValue: async () => 3 + }; + await expect(item.getValue()).resolves.toBe(3); + + (item as unknown as { _value?: { getValue: () => Promise } })._value = { + getValue: async () => 'bad' + }; + await expect(item.getValue()).resolves.toBeUndefined(); + }); + + it('computes sizes, member lookups, and offsets', async () => { + const item = new ScvdVar(undefined); + const member = new ScvdVar(item); + item.size = '2'; + expect(item.getTargetSize()).toBe(2); + item.size = undefined; + expect(item.getTargetSize()).toBe(2); + expect(item.getIsPointer()).toBe(false); + expect(item.getMember('m')).toBeUndefined(); + expect(item.getElementRef()).toBeUndefined(); + + const typeStub = { + getTypeSize: () => 4, + getIsPointer: () => true, + getMember: () => member, + getValueType: () => 'uint32' + }; + (item as unknown as { _type?: { getTypeSize: () => number; getIsPointer: () => boolean; getMember: (p: string) => ScvdVar; getValueType: () => string } })._type = typeStub; + item.size = '3'; + + expect(item.getTypeSize()).toBe(4); + expect(item.getTargetSize()).toBe(12); + expect(item.getVirtualSize()).toBe(12); + expect(item.getIsPointer()).toBe(true); + expect(item.getMember('m')).toBe(member); + expect(item.getElementRef()).toBe(typeStub); + expect(item.getValueType()).toBe('uint32'); + + item.offset = '4'; + expect(item.offset).toBeDefined(); + item.offset = undefined; + expect(item.offset).toBeDefined(); + + (item as unknown as { _offset?: { getValue: () => Promise } })._offset = { + getValue: async () => 8 + }; + await expect(item.getMemberOffset()).resolves.toBe(8); + + (item as unknown as { _offset?: { getValue: () => Promise } })._offset = { + getValue: async () => 'bad' + }; + await expect(item.getMemberOffset()).resolves.toBe(0); + + (item as unknown as { _offset?: undefined })._offset = undefined; + await expect(item.getMemberOffset()).resolves.toBe(0); + }); + + it('defaults target size to 1 when no size or type is set', () => { + const item = new ScvdVar(undefined); + expect(item.getTargetSize()).toBe(1); + }); + + it('updates the data type when set repeatedly', () => { + const item = new ScvdVar(undefined); + item.type = 'uint32_t'; + const type = item.type; + item.type = 'uint16_t'; + expect(item.type).toBe(type); + + item.type = undefined; + expect(item.type).toBe(type); + }); +}); diff --git a/src/views/component-viewer/test/unit/parser-evaluator/eval-interface/scvd-eval-interface.test.ts b/src/views/component-viewer/test/unit/parser-evaluator/eval-interface/scvd-eval-interface.test.ts new file mode 100644 index 00000000..e96a3e37 --- /dev/null +++ b/src/views/component-viewer/test/unit/parser-evaluator/eval-interface/scvd-eval-interface.test.ts @@ -0,0 +1,420 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEvalInterface helpers and intrinsics. + */ + +import { ScvdEvalInterface } from '../../../../scvd-eval-interface'; +import type { RefContainer } from '../../../../parser-evaluator/model-host'; +import type { MemoryHost } from '../../../../data-host/memory-host'; +import type { RegisterHost } from '../../../../data-host/register-host'; +import type { ScvdDebugTarget } from '../../../../scvd-debug-target'; +import type { FormatSegment } from '../../../../parser-evaluator/parser'; +import { ScvdFormatSpecifier } from '../../../../model/scvd-format-specifier'; +import { ScvdNode } from '../../../../model/scvd-node'; +import { ScvdMember } from '../../../../model/scvd-member'; + +class DummyNode extends ScvdNode { + constructor( + name: string | undefined, + private readonly opts: Partial<{ + targetSize: number; + virtualSize: number; + arraySize: number; + isPointer: boolean; + memberOffset: number; + valueType: string; + symbolMap: Map; + }> = {} + ) { + super(undefined); + this.name = name; + } + public override getTargetSize(): number | undefined { return this.opts.targetSize; } + public override getVirtualSize(): number | undefined { return this.opts.virtualSize; } + public override async getArraySize(): Promise { return this.opts.arraySize; } + public override getIsPointer(): boolean { return this.opts.isPointer ?? false; } + public override getDisplayLabel(): string { return this.name ?? ''; } + public override getMemberOffset(): Promise { return Promise.resolve(this.opts.memberOffset); } + public override getMember(name: string): ScvdNode | undefined { + const map = this.opts.symbolMap; + return map?.get(name); + } + public override getValueType(): string | undefined { return this.opts.valueType; } +} + +class LocalFakeMember extends ScvdMember { + constructor() { + super(undefined); + } + public override getTargetSize(): number | undefined { return 4; } + public override async getEnum(_value: number) { + return { getGuiName: async () => 'ENUM_READY' } as unknown as Awaited>; + } +} + +function makeEval(overrides: Partial & Partial & Partial = {}) { + const merged = overrides ?? {}; + const memHost: Partial = { + readValue: jest.fn(), + writeValue: jest.fn(), + getArrayElementCount: jest.fn().mockReturnValue(3), + getElementTargetBase: jest.fn().mockReturnValue(0xbeef), + ...merged + }; + const regHost: Partial = { + read: jest.fn().mockReturnValue(undefined), + write: jest.fn(), + ...merged + }; + const debugTarget: Partial = { + readRegister: jest.fn().mockResolvedValue(123), + calculateMemoryUsage: jest.fn().mockResolvedValue(0xabcd), + getSymbolSize: jest.fn().mockResolvedValue(undefined), + getNumArrayElements: jest.fn().mockResolvedValue(7), + getTargetIsRunning: jest.fn().mockResolvedValue(true), + findSymbolAddress: jest.fn().mockResolvedValue(undefined), + ...merged + }; + const formatter = new ScvdFormatSpecifier(); + const evalIf = new ScvdEvalInterface( + memHost as MemoryHost, + regHost as RegisterHost, + debugTarget as ScvdDebugTarget, + formatter + ); + return { evalIf, memHost: memHost as MemoryHost, regHost: regHost as RegisterHost, debugTarget: debugTarget as ScvdDebugTarget }; +} + +describe('ScvdEvalInterface intrinsics and helpers', () => { + it('reads register with cache and normalization', async () => { + const regHost = { + read: jest.fn().mockReturnValueOnce(undefined).mockReturnValueOnce(999), + write: jest.fn() + } as unknown as RegisterHost; + const debugTarget = { readRegister: jest.fn().mockResolvedValue(321) } as unknown as ScvdDebugTarget; + const { evalIf } = makeEval({ ...regHost, ...debugTarget }); + + await expect(evalIf.__GetRegVal(' r0 ')).resolves.toBe(321); + expect(regHost.write).toHaveBeenCalledWith('r0', 321); + await expect(evalIf.__GetRegVal(' r0 ')).resolves.toBe(999); + }); + + it('__Symbol_exists and __FindSymbol normalize names and map found/not found', async () => { + const findSymbolAddress = jest.fn().mockResolvedValue(0x1234); + const { evalIf } = makeEval({ findSymbolAddress }); + await expect(evalIf.__Symbol_exists(' ')).resolves.toBe(0); + await expect(evalIf.__Symbol_exists('MySym')).resolves.toBe(1); + await expect(evalIf.__FindSymbol('MySym')).resolves.toBe(0x1234); + }); + + it('__CalcMemUsed forwards params', async () => { + const calculateMemoryUsage = jest.fn().mockResolvedValue(0xf00d); + const { evalIf } = makeEval({ calculateMemoryUsage }); + await expect(evalIf.__CalcMemUsed(1, 2, 3, 4)).resolves.toBe(0xf00d); + expect(calculateMemoryUsage).toHaveBeenCalledWith(1, 2, 3, 4); + }); + + it('__size_of prefers size then falls back to element count', async () => { + const debugTarget: Partial = { + getSymbolSize: jest.fn().mockResolvedValueOnce(16).mockResolvedValueOnce(undefined), + getNumArrayElements: jest.fn().mockResolvedValue(5) + }; + const { evalIf } = makeEval(debugTarget); + await expect(evalIf.__size_of('sym')).resolves.toBe(16); + await expect(evalIf.__size_of('sym')).resolves.toBe(5); + }); + + it('__Offset_of and __Running', async () => { + const member = new DummyNode('m', { memberOffset: 12 }); + const container: RefContainer = { base: new DummyNode('base', { symbolMap: new Map([['member', member]]) }), current: undefined, valueType: undefined }; + const { evalIf, debugTarget } = makeEval({ getTargetIsRunning: jest.fn().mockResolvedValue(false) }); + await expect(evalIf.__Offset_of(container, 'member')).resolves.toBe(12); + await expect(evalIf.__Offset_of(container, 'missing')).resolves.toBeUndefined(); + await expect(evalIf.__Running()).resolves.toBe(0); + expect(debugTarget.getTargetIsRunning).toHaveBeenCalled(); + }); + + it('_count and _addr defer to MemoryHost', async () => { + const base = new DummyNode('arr'); + const container: RefContainer = { base, current: base, valueType: undefined }; + const memHost = { + getArrayElementCount: jest.fn().mockReturnValue(10), + getElementTargetBase: jest.fn().mockReturnValue(0xbeef) + } as unknown as MemoryHost; + const { evalIf } = makeEval(memHost); + expect(await evalIf._count(container)).toBe(10); + expect(await evalIf._addr(container)).toBe(0xbeef); + }); + + it('getByteWidth handles pointers, arrays, and logs missing size', async () => { + const ptrNode = new DummyNode('ptr', { isPointer: true }); + const sizedNode = new DummyNode('arr', { targetSize: 2, arraySize: 3 }); + const missing = new DummyNode('missing'); + const { evalIf } = makeEval(); + await expect(evalIf.getByteWidth(ptrNode)).resolves.toBe(4); + await expect(evalIf.getByteWidth(sizedNode)).resolves.toBe(6); + jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(evalIf.getByteWidth(missing)).resolves.toBeUndefined(); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('getElementStride handles pointer, virtual size, target size, and missing stride', async () => { + const ptrNode = new DummyNode('ptr', { isPointer: true }); + const virtualNode = new DummyNode('virt', { virtualSize: 5 }); + const sizedNode = new DummyNode('sized', { targetSize: 3 }); + const missing = new DummyNode('missing'); + jest.spyOn(console, 'error').mockImplementation(() => {}); + const { evalIf } = makeEval(); + expect(await evalIf.getElementStride(ptrNode)).toBe(4); + expect(await evalIf.getElementStride(virtualNode)).toBe(5); + expect(await evalIf.getElementStride(sizedNode)).toBe(3); + expect(await evalIf.getElementStride(missing)).toBe(0); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('getMemberOffset logs when undefined', async () => { + const member = new DummyNode('m'); + const { evalIf } = makeEval(); + jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(evalIf.getMemberOffset(new DummyNode('b'), member)).resolves.toBeUndefined(); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('resolveColonPath and getElementRef fall back to undefined', async () => { + const child = new DummyNode('child'); + const base = new DummyNode('base', { symbolMap: new Map([['child', child]]) }); + const container: RefContainer = { base, current: base, valueType: undefined }; + const { evalIf } = makeEval(); + await expect(evalIf.resolveColonPath(container, ['a', 'b'])).resolves.toBeUndefined(); + await expect(evalIf.getElementRef(base)).resolves.toBeUndefined(); + }); + + it('read/write value wrap host errors', async () => { + const memHost = { + readValue: jest.fn(() => { throw new Error('boom'); }), + writeValue: jest.fn(() => { throw new Error('boom'); }) + } as unknown as MemoryHost; + const { evalIf } = makeEval(memHost); + jest.spyOn(console, 'error').mockImplementation(() => {}); + const container: RefContainer = { base: new DummyNode('b'), current: new DummyNode('b'), valueType: undefined }; + expect(await evalIf.readValue(container)).toBeUndefined(); + expect(await evalIf.writeValue(container, 1)).toBeUndefined(); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('getSymbolRef/getMemberRef/getValueType resolve through container', async () => { + const member = new DummyNode('m'); + const base = { + getSymbol: jest.fn().mockReturnValue(member), + getMember: jest.fn().mockReturnValue(member), + getDisplayLabel: () => 'base', + name: 'base' + } as unknown as ScvdNode; + const container: RefContainer = { base, current: base, valueType: undefined }; + const { evalIf } = makeEval(); + expect(await evalIf.getSymbolRef(container, 'm')).toBe(member); + expect(await evalIf.getMemberRef(container, 'm')).toBe(member); + expect(await evalIf.getValueType({ ...container, current: new DummyNode('c', { valueType: 'float64' }) })).toBe('float64'); + }); + + it('covers scalar normalization, width hints, and special _addr case', async () => { + const { evalIf } = makeEval(); + const addrNode = new DummyNode('_addr'); + const addrContainer: RefContainer = { base: addrNode, current: addrNode, valueType: undefined }; + const addrInfo = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise }) + .getScalarInfo(addrContainer); + expect(addrInfo).toEqual({ kind: 'unknown', bits: 32, widthBytes: 4 }); + + const hintContainer: RefContainer = { base: new DummyNode('h', { targetSize: 0 }), current: undefined, widthBytes: 2, valueType: undefined }; + const hintInfo = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise<{ bits?: number; widthBytes?: number; kind: string }> }) + .getScalarInfo(hintContainer); + expect(hintInfo.bits).toBe(32); + expect(hintInfo.widthBytes).toBe(2); + + const widen = await (evalIf as unknown as { normalizeScalarType(v: unknown): { kind: string; name?: string; bits?: number } }).normalizeScalarType({ kind: 'int', name: 'custom', bits: 128 }); + expect(widen.bits).toBe(128); + }); + + it('covers scalar info for array types', async () => { + const arrayNode = new DummyNode('arr', { arraySize: 4, valueType: 'uint8_t' }); + const container: RefContainer = { base: arrayNode, current: arrayNode, valueType: undefined }; + const { evalIf } = makeEval(); + const formatted = await evalIf.formatPrintf('x', 1, container); + expect(formatted).toBeDefined(); + }); + + it('normalizeScalarType and helpers handle undefined and invalid pointers', async () => { + const { evalIf, debugTarget } = makeEval({ readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3])) }); + const norm = (evalIf as unknown as { normalizeScalarType(v: unknown): unknown }).normalizeScalarType(' double64 '); + expect(norm).toEqual({ kind: 'float', name: 'double64', bits: 64 }); + + const readBytes = await (evalIf as unknown as { readBytesFromPointer(addr: number, len: number): Promise }) + .readBytesFromPointer(NaN, 4); + expect(readBytes).toBeUndefined(); + const readOk = await (evalIf as unknown as { readBytesFromPointer(addr: number, len: number): Promise }) + .readBytesFromPointer(0x10, 2); + expect(readOk).toEqual(new Uint8Array([1, 2, 3])); + expect(debugTarget.readMemory).toHaveBeenCalled(); + + const sym = await (evalIf as unknown as { findSymbolAddressNormalized(name: string | undefined): Promise }) + .findSymbolAddressNormalized(undefined); + expect(sym).toBeUndefined(); + }); + + it('covers byte width fallback and bit clamping', async () => { + const { evalIf } = makeEval(); + const wideNode = new DummyNode('wide', { targetSize: 0, valueType: 'uint128_t' }); + const info = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise<{ bits?: number; widthBytes?: number; kind: string }> }) + .getScalarInfo({ base: wideNode, current: wideNode, widthBytes: undefined, valueType: undefined } as unknown as RefContainer); + expect(info.bits).toBe(8); // regex picks 8 in 128, then clamped logic keeps scalar bits + + jest.spyOn(console, 'error').mockImplementation(() => {}); + const sizedViaHelper = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise<{ bits?: number; widthBytes?: number; kind: string }> }) + .getScalarInfo({ base: new DummyNode('viaHelper'), current: new DummyNode('viaHelper'), valueType: undefined } as unknown as RefContainer); + expect(sizedViaHelper.bits).toBeDefined(); + (console.error as unknown as jest.Mock).mockRestore(); + + const viaByteWidth = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise<{ bits?: number; widthBytes?: number; kind: string }> }) + .getScalarInfo({ base: new DummyNode('viaBW', { targetSize: 0 }), current: new DummyNode('viaBW', { targetSize: 0 }), widthBytes: undefined, valueType: undefined } as unknown as RefContainer); + expect(viaByteWidth.bits).toBeDefined(); + }); + + it('covers member offset success, read/write success, and _count/_addr undefined', async () => { + const memHost = { + readValue: jest.fn().mockReturnValue(7), + writeValue: jest.fn(), + getArrayElementCount: jest.fn().mockReturnValue(5), + getElementTargetBase: jest.fn().mockReturnValue(0xabc) + } as unknown as MemoryHost; + const { evalIf } = makeEval(memHost); + const member = new DummyNode('m', { memberOffset: 8 }); + await expect(evalIf.getMemberOffset(new DummyNode('b'), member)).resolves.toBe(8); + + const container: RefContainer = { base: new DummyNode('b'), current: new DummyNode('b'), valueType: undefined }; + expect(await evalIf.readValue(container)).toBe(7); + expect(await evalIf.writeValue(container, 9)).toBe(9); + + expect(await evalIf._count({ base: new DummyNode(undefined), current: new DummyNode(undefined), valueType: undefined } as unknown as RefContainer)).toBeUndefined(); + expect(await evalIf._addr({ base: new DummyNode(undefined), current: new DummyNode(undefined), valueType: undefined } as unknown as RefContainer)).toBeUndefined(); + + const regHost = { read: jest.fn().mockReturnValueOnce(undefined).mockReturnValueOnce(undefined), write: jest.fn() } as unknown as RegisterHost; + const debugTarget = { readRegister: jest.fn().mockResolvedValue(5) } as unknown as ScvdDebugTarget; + const { evalIf: evalReg } = makeEval({ ...memHost, ...regHost, ...debugTarget }); + await expect(evalReg.__GetRegVal(' ')).resolves.toBeUndefined(); + await expect(evalReg.__GetRegVal('r1')).resolves.toBe(5); + const { evalIf: evalSize } = makeEval({ getSymbolSize: jest.fn().mockResolvedValue(undefined), getNumArrayElements: jest.fn().mockResolvedValue(undefined) } as unknown as ScvdDebugTarget); + await expect(evalSize.__size_of('sym')).resolves.toBeUndefined(); + }); + + it('covers formatPrintf fallbacks when addresses are missing', async () => { + const readUint8ArrayStrFromPointer = jest.fn().mockResolvedValue(undefined); + const findSymbolNameAtAddress = jest.fn().mockResolvedValue(undefined); + const debugTarget = { readUint8ArrayStrFromPointer, findSymbolNameAtAddress, readMemory: jest.fn().mockResolvedValue(undefined) } as unknown as ScvdDebugTarget; + const { evalIf } = makeEval(debugTarget); + const container: RefContainer = { base: new DummyNode('b'), current: new DummyNode('b'), valueType: undefined }; + jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(evalIf.formatPrintf('C', 'noaddr' as unknown as number, container)).resolves.toBe('noaddr'); + await expect(evalIf.formatPrintf('S', 'noaddr' as unknown as number, container)).resolves.toBe('noaddr'); + const nOut = await evalIf.formatPrintf('N', 0x9999, container); + expect(nOut).toBeDefined(); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('covers formatPrintf data paths (context, enums, IPv4/IPv6, toNumeric)', async () => { + const symbolMap = new Map([[0x2000, 'CTXSYM']]); + const memoryMap = new Map([ + [0x10, new Uint8Array([1, 2, 3, 4])], + [0x20, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])], + [0x30, new Uint8Array([1, 2, 3, 4, 5, 6])] + ]); + const debugTarget = { + findSymbolContextAtAddress: jest.fn().mockResolvedValue('CTX'), + findSymbolNameAtAddress: jest.fn().mockImplementation((addr: number) => symbolMap.get(addr)), + readUint8ArrayStrFromPointer: jest.fn().mockResolvedValue(new Uint8Array([65, 0, 0, 0])), + readMemory: jest.fn(async (addr: number, len: number) => (memoryMap.get(addr)?.subarray(0, len))) + } as unknown as ScvdDebugTarget; + const formatterEval = new ScvdEvalInterface({} as MemoryHost, {} as RegisterHost, debugTarget, new ScvdFormatSpecifier()); + const member = new LocalFakeMember(); + const container: RefContainer = { base: member, current: member, valueType: undefined }; + expect(await formatterEval.formatPrintf('C', 0x2000, container)).toBe('CTX'); + expect(await formatterEval.formatPrintf('S', 0x2000, container)).toBe('CTXSYM'); + expect(await formatterEval.formatPrintf('E', 1, container)).toBe('ENUM_READY'); + expect(await formatterEval.formatPrintf('I', 0x10, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('I', new Uint8Array([1, 2, 3, 4]), container)).toBeDefined(); + expect(await formatterEval.formatPrintf('J', 0x20, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('J', new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]), container)).toBeDefined(); + expect(await formatterEval.formatPrintf('M', 0x30, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('M', new Uint8Array([1, 2, 3, 4, 5, 6]), container)).toBeDefined(); + expect(await formatterEval.formatPrintf('U', 0x9999, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('x', true as unknown as number, container)).toBe('0x1'); + expect(await formatterEval.formatPrintf('x', '7' as unknown as number, container)).toBe('0x7'); + + const noContextDebug = { + findSymbolContextAtAddress: jest.fn().mockResolvedValue(undefined), + findSymbolNameAtAddress: jest.fn().mockResolvedValue(undefined), + readUint8ArrayStrFromPointer: jest.fn().mockResolvedValue(undefined), + readMemory: jest.fn().mockResolvedValue(undefined) + } as unknown as ScvdDebugTarget; + const formatterEval2 = new ScvdEvalInterface({} as MemoryHost, {} as RegisterHost, noContextDebug, new ScvdFormatSpecifier()); + expect(await formatterEval2.formatPrintf('C', 0x1234, container)).toBe('0x00001234'); + }); + + it('covers scalar width via getByteWidth and register read returning undefined', async () => { + const { evalIf } = makeEval(); + const node = new DummyNode('bw', { targetSize: 0, valueType: 'int' }); + (evalIf as unknown as { getByteWidth(ref: ScvdNode): Promise }).getByteWidth = jest.fn().mockResolvedValue(10); + const info = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise<{ bits?: number; widthBytes?: number; kind: string }> }) + .getScalarInfo({ base: node, current: node, valueType: undefined } as unknown as RefContainer); + expect(info.bits).toBe(64); // clamp from 80 + + const regHost = { read: jest.fn().mockReturnValue(undefined), write: jest.fn() } as unknown as RegisterHost; + const debugTarget = { readRegister: jest.fn().mockResolvedValue(undefined) } as unknown as ScvdDebugTarget; + const { evalIf: evalReg } = makeEval({ ...regHost, ...debugTarget }); + await expect(evalReg.__GetRegVal('r2')).resolves.toBeUndefined(); + }); + + it('covers default scalar bits, toNumeric branches, and formatPrintf fallbacks', async () => { + const { evalIf } = makeEval(); + jest.spyOn(console, 'error').mockImplementation(() => {}); + const scalarInfo = await (evalIf as unknown as { getScalarInfo(c: RefContainer): Promise<{ bits?: number; kind: string }> }) + .getScalarInfo({ base: new DummyNode('plain', { valueType: 'int' }), current: new DummyNode('plain', { valueType: 'int' }), valueType: undefined } as unknown as RefContainer); + expect(scalarInfo.bits).toBe(32); + + const dbg = { + readMemory: jest.fn().mockResolvedValue(undefined), + readUint8ArrayStrFromPointer: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3])), + findSymbolContextAtAddress: jest.fn().mockResolvedValue(undefined), + findSymbolNameAtAddress: jest.fn().mockResolvedValue(undefined) + } as unknown as ScvdDebugTarget; + const formatterEval = new ScvdEvalInterface({} as MemoryHost, {} as RegisterHost, dbg, new ScvdFormatSpecifier()); + const container: RefContainer = { base: new DummyNode('b'), current: new DummyNode('b'), valueType: undefined }; + expect(await formatterEval.formatPrintf('x', BigInt(5) as unknown as number, container)).toBe('0x5'); + expect(await formatterEval.formatPrintf('x', ({}) as unknown as number, container)).toBe('NaN'); + expect(await formatterEval.formatPrintf('I', 0x1, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('I', 'text' as unknown as number, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('J', 0x1, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('J', 'text' as unknown as number, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('N', 0x1, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('M', 'str' as unknown as number, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('U', 'str' as unknown as number, container)).toBeDefined(); + expect(await formatterEval.formatPrintf('Z' as unknown as FormatSegment['spec'], 1, container)).toBeDefined(); + (console.error as unknown as jest.Mock).mockRestore(); + }); +}); diff --git a/src/views/component-viewer/test/unit/parser-evaluator/evaluator/evaluator.test.ts b/src/views/component-viewer/test/unit/parser-evaluator/evaluator/evaluator.test.ts new file mode 100644 index 00000000..ca364c03 --- /dev/null +++ b/src/views/component-viewer/test/unit/parser-evaluator/evaluator/evaluator.test.ts @@ -0,0 +1,673 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for evaluator helpers using real parser ASTs and a minimal DataHost. + */ + +import { parseExpression, type FormatSegment, type ASTNode, type EvalPointCall, type CallExpression, type AssignmentExpression, type ConditionalExpression, type BinaryExpression, type UpdateExpression, type UnaryExpression, type ArrayIndex, type MemberAccess, type Identifier, type PrintfExpression, type TextSegment } from '../../../../parser-evaluator/parser'; +import { evaluateParseResult, EvalContext, evalNode } from '../../../../parser-evaluator/evaluator'; +import type { RefContainer, EvalValue, ScalarType } from '../../../../parser-evaluator/model-host'; +import type { FullDataHost } from '../../../integration/helpers/full-data-host'; +import { ScvdNode } from '../../../../model/scvd-node'; + +class FakeNode extends ScvdNode { + constructor( + public readonly id: string, + parent?: ScvdNode, + public value: EvalValue = undefined, + private members: Map = new Map(), + ) { + super(parent); + } + public async setValue(v: number | string): Promise { + this.value = v; + return v; + } + public async getValue(): Promise | undefined> { + return this.value as unknown as string | number | bigint | Uint8Array | undefined; + } + public getSymbol(name: string): ScvdNode | undefined { + return this.members.get(name); + } +} + +class Host implements FullDataHost { + constructor(private values: Map) {} + private setCurrent(container: RefContainer, node: FakeNode): FakeNode { + container.current = node; + return node; + } + async resolveColonPath(): Promise { + return undefined; + } + async getSymbolRef(container: RefContainer, name: string): Promise { + if (!this.values.has(name)) { + return undefined; + } + const n = this.values.get(name); + return n ? this.setCurrent(container, n) : undefined; + } + async getMemberRef(container: RefContainer, property: string): Promise { + const cur = container.current as FakeNode | undefined; + const m = cur?.getSymbol(property) as FakeNode | undefined; + if (m) { + return this.setCurrent(container, m); + } + return undefined; + } + async readValue(container: RefContainer): Promise { + return (container.current as FakeNode | undefined)?.value; + } + async writeValue(container: RefContainer, value: EvalValue): Promise { + const node = container.current as FakeNode | undefined; + if (typeof value === 'number' || typeof value === 'string') { + await node?.setValue(value); + } + return value; + } + async getByteWidth(ref?: ScvdNode): Promise { + const cur = ref as FakeNode | undefined; + const val = cur?.['value'] as EvalValue; + return typeof val === 'bigint' ? 8 : 1; + } + async getElementStride(_ref: ScvdNode): Promise { + return 1; + } + async getMemberOffset(_base: ScvdNode, _member: ScvdNode): Promise { + return undefined; + } + async getElementRef(ref: ScvdNode): Promise { + const node = this.values.get((ref as FakeNode).id + '[0]'); + return node ?? this.values.get((ref as FakeNode).id); + } + async __GetRegVal(): Promise { + return undefined; + } + async __FindSymbol(): Promise { + return undefined; + } + async __CalcMemUsed(): Promise { + return undefined; + } + async __size_of(): Promise { + return undefined; + } + async __Symbol_exists(): Promise { + return undefined; + } + async __Offset_of(): Promise { + return undefined; + } + async __Running(): Promise { + return undefined; + } + async _count(): Promise { + return undefined; + } + async _addr(): Promise { + return undefined; + } + async formatPrintf(): Promise { + return undefined; + } + async getValueType(container: RefContainer): Promise { + const cur = container.current as FakeNode | undefined; + const val = cur?.['value'] as EvalValue; + if (typeof val === 'bigint') { + return { kind: 'uint', bits: 64 }; + } + if (typeof val === 'number') { + return { kind: 'int', bits: 32 }; + } + return undefined; + } +} + +function evalExpr(expr: string, host: Host, base: ScvdNode): Promise { + const pr = parseExpression(expr, false); + const ctx = new EvalContext({ data: host, container: base }); + return evaluateParseResult(pr, ctx); +} + +describe('evaluator', () => { + it('handles arithmetic, bitwise, shifts, and comparisons', async () => { + const base = new FakeNode('base'); + const values = new Map([ + ['a', new FakeNode('a', base, 5)], + ['b', new FakeNode('b', base, 2)], + ['big', new FakeNode('big', base, 2n)], + ['big2', new FakeNode('big2', base, 3n)], + ]); + const host = new Host(values); + + await expect(evalExpr('a + b - 1', host, base)).resolves.toBe(6); + await expect(evalExpr('a * b', host, base)).resolves.toBe(10); + await expect(evalExpr('a / b', host, base)).resolves.toBe(2); + await expect(evalExpr('a % b', host, base)).resolves.toBe(1); + await expect(evalExpr('a & b', host, base)).resolves.toBe(0); + await expect(evalExpr('a ^ b', host, base)).resolves.toBe(7); + await expect(evalExpr('a | b', host, base)).resolves.toBe(7); + await expect(evalExpr('a << b', host, base)).resolves.toBe(20); + await expect(evalExpr('a >> b', host, base)).resolves.toBe(1); + await expect(evalExpr('a == b', host, base)).resolves.toBe(0); + await expect(evalExpr('a != b', host, base)).resolves.toBe(1); + await expect(evalExpr('a < b', host, base)).resolves.toBe(0); + await expect(evalExpr('a <= b', host, base)).resolves.toBe(0); + await expect(evalExpr('a > b', host, base)).resolves.toBe(1); + await expect(evalExpr('a >= b', host, base)).resolves.toBe(1); + + const pr = parseExpression('big + big2', false); + const ctx = new EvalContext({ data: host, container: base }); + await expect(evalNode(pr.ast, ctx)).resolves.toBe(5n); + await expect(evaluateParseResult(pr, ctx)).resolves.toBeUndefined(); + }); + + it('covers assignment, update, conditionals, and logical ops', async () => { + const base = new FakeNode('base'); + const x = new FakeNode('x', base, 1); + const y = new FakeNode('y', base, 0); + const values = new Map([['x', x], ['y', y]]); + const host = new Host(values); + + await expect(evalExpr('x = 3', host, base)).resolves.toBe(3); + expect(x['value']).toBe(3); + await expect(evalExpr('++x', host, base)).resolves.toBe(4); + await expect(evalExpr('--x', host, base)).resolves.toBe(3); + await expect(evalExpr('x ? 5 : 6', host, base)).resolves.toBe(5); + await expect(evalExpr('x && y', host, base)).resolves.toBe(0); + await expect(evalExpr('x || y', host, base)).resolves.toBe(3); + }); + + it('handles member access, array indexing, and error paths', async () => { + const base = new FakeNode('base'); + const obj = new FakeNode('obj', base, undefined, new Map([['m', new FakeNode('m', base, 9)]])); + const arrElem = new FakeNode('arr[0]', base, 7); + const arr = new FakeNode('arr', base, undefined, new Map([['0', arrElem]])); + const values = new Map([['obj', obj], ['arr', arr], ['arr[0]', arrElem]]); + const host = new Host(values); + + await expect(evalExpr('obj.m', host, base)).resolves.toBe(9); + await expect(evalExpr('arr[0]', host, base)).resolves.toBe(7); + + // Unknown symbol triggers error and normalizeEvaluateResult returns undefined + const pr = parseExpression('missing', false); + const ctx = new EvalContext({ data: host, container: base }); + jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect(evaluateParseResult(pr, ctx)).resolves.toBeUndefined(); + (console.error as unknown as jest.Mock).mockRestore(); + }); + + it('applies member offsets when provided by host', async () => { + class OffsetHost extends Host { + async getMemberOffset(): Promise { + return 8; + } + } + + const base = new FakeNode('base'); + const child = new FakeNode('child', base, 1); + const obj = new FakeNode('obj', base, undefined, new Map([['child', child]])); + const values = new Map([['obj', obj], ['child', child]]); + const host = new OffsetHost(values); + const ctx = new EvalContext({ data: host, container: base }); + + await expect(evalNode(parseExpression('obj.child', false).ast, ctx)).resolves.toBe(child['value']); + expect(ctx.container.offsetBytes).toBe(8); + }); + + it('evaluates a complex conditional expression', async () => { + const base = new FakeNode('base'); + const count = new FakeNode('Count', base, 2); + const values = new Map([['Count', count]]); + const host = new Host(values); + const expr = '0==( (Count==0) || (Count==1) || (Count==8) || (Count==9) || (Count==10) )'; + await expect(evalExpr(expr, host, base)).resolves.toBe(1); + }); + + it('covers intrinsics, pseudo members, and string/unary paths', async () => { + const base = new FakeNode('base'); + const arrElem = new FakeNode('arr[0]', base, 1); + const arr = new FakeNode('arr', base, undefined, new Map([['0', arrElem]])); + const str = new FakeNode('str', base, 0); + const values = new Map([['arr', arr], ['arr[0]', arrElem], ['str', str]]); + const host = new Host(values); + host._count = async () => 2; + host._addr = async () => 0x1000; + host.__Running = async () => 1; + + await expect(evalExpr('__Running', host, base)).resolves.toBe(1); + await expect(evalExpr('arr._count', host, base)).resolves.toBe(2); + await expect(evalExpr('arr._addr', host, base)).resolves.toBe(0x1000); + await expect(evalExpr('"x" + 5', host, base)).resolves.toBe('x5'); + await expect(evalExpr('~1', host, base)).resolves.toBe(4294967294); + await expect(evalExpr('!0', host, base)).resolves.toBe(1); + }); +}); + +class BranchNode extends ScvdNode { + private readonly members: Map; + public value: EvalValue; + constructor(name: string, parent?: ScvdNode, value: EvalValue = 0, members: Record = {}) { + super(parent); + this.name = name; + this.value = value; + this.members = new Map(Object.entries(members)); + } + public async setValue(v: string | number): Promise { + this.value = v; + return v; + } + public async getValue(): Promise { + const v = this.value; + if (typeof v === 'boolean') { + return v ? 1 : 0; + } + if (typeof v === 'function') { + return undefined; + } + if (typeof v === 'string' || typeof v === 'number' || typeof v === 'bigint' || v instanceof Uint8Array) { + return v; + } + return undefined; + } + public getMember(property: string): BranchNode | undefined { return this.members.get(property); } + public setMember(property: string, node: BranchNode) { this.members.set(property, node); } +} + +class BranchHost implements FullDataHost { + constructor(private readonly values: Map) {} + async resolveColonPath(): Promise { + return undefined; + } + + async getSymbolRef(container: RefContainer, name: string, _forWrite?: boolean): Promise { + const n = this.values.get(name); + container.current = n; + container.anchor = n; + return n; + } + async getMemberRef(container: RefContainer, property: string, _forWrite?: boolean): Promise { + const cur = container.current as BranchNode | undefined; + const member = cur?.getMember(property); + if (member) { + container.current = member; + } + return member; + } + async readValue(container: RefContainer): Promise { + const cur = container.current as BranchNode | undefined; + const v = await cur?.getValue(); + container.current = undefined; // force evaluateFormatSegmentValue to recover via findReferenceNode + return v; + } + async writeValue(container: RefContainer, value: EvalValue): Promise { + if (typeof value === 'number' || typeof value === 'string') { + await (container.current as BranchNode | undefined)?.setValue(value); + } + return value; + } + async getElementStride(_ref: ScvdNode): Promise { + return 1; + } + async getMemberOffset(_base: ScvdNode, _member: ScvdNode): Promise { + return undefined; + } + async getElementRef(ref: ScvdNode): Promise { + return ref.getElementRef(); + } + async __GetRegVal(): Promise { + return undefined; + } + async __FindSymbol(): Promise { + return undefined; + } + async __CalcMemUsed(): Promise { + return undefined; + } + async __size_of(): Promise { + return undefined; + } + async __Symbol_exists(): Promise { + return undefined; + } + async __Offset_of(): Promise { + return undefined; + } + async __Running(): Promise { + return undefined; + } + async _count(): Promise { + return undefined; + } + async _addr(): Promise { + return undefined; + } + async formatPrintf(): Promise { + return undefined; + } + async getValueType(container: RefContainer): Promise { + const cur = container.current as BranchNode | undefined; + if (!cur) { + return undefined; + } + const name = cur.name ?? ''; + if (name.startsWith('u')) { + return 'uint32'; + } + if (name.startsWith('f')) { + return 'float32'; + } + if (name.startsWith('q')) { + return 'uint64'; + } + return { kind: 'int', bits: 32 }; + } + async getByteWidth(ref: ScvdNode): Promise { + const n = ref as BranchNode; + return typeof n.value === 'bigint' ? 8 : 4; + } +} + +function segFromAst(ast: ASTNode, spec = 'd'): FormatSegment { + return { kind: 'FormatSegment', spec, value: ast, start: 0, end: 0 }; +} + +describe('evaluator edge', () => { + it('hits float/unsigned math and shift branches', async () => { + const base = new BranchNode('base'); + const values = new Map([ + ['f1', new BranchNode('f1', base, 5.5)], + ['f2', new BranchNode('f2', base, 2.5)], + ['u1', new BranchNode('u1', base, 5)], + ['u2', new BranchNode('u2', base, 2)], + ]); + const host = new BranchHost(values); + const ctx = new EvalContext({ data: host, container: base }); + + expect(await evalNode(parseExpression('f1 / f2', false).ast, ctx)).toBeCloseTo(2.2); + expect(await evalNode(parseExpression('f1 % f2', false).ast, ctx)).toBe(1); + expect(await evalNode(parseExpression('5 >> 1', false).ast, ctx)).toBe(2); + expect(await evalNode(parseExpression('u1 / u2', false).ast, ctx)).toBe(2); // unsigned integer path + expect(await evalNode(parseExpression('u1 % u2', false).ast, ctx)).toBe(1); + await expect(evaluateParseResult(parseExpression('5 / 0', false), ctx)).resolves.toBeUndefined(); // division by zero handled + }); + + it('normalizes scalar types from strings and booleans/bigints', async () => { + const base = new BranchNode('base'); + const host = new BranchHost(new Map([['q1', new BranchNode('q1', base, 1n)]])); + const ctx = new EvalContext({ data: host, container: base }); + // bigint result is normalized to undefined + await expect(evaluateParseResult(parseExpression('q1 + 1', false), ctx)).resolves.toBeUndefined(); + // boolean normalized to 1 + await expect(evaluateParseResult(parseExpression('!0', false), ctx)).resolves.toBe(1); + + const alt = new BranchNode('alt'); + await expect(evaluateParseResult(parseExpression('1+2', false), ctx, alt)).resolves.toBe(3); + }); + + it('routes intrinsics and error paths', async () => { + const base = new BranchNode('base'); + const host = new BranchHost(new Map([['reg', new BranchNode('reg', base, 0)]])); + host.__Running = async () => 1; + host.__GetRegVal = async () => 7; + host.__FindSymbol = async () => 9; + host.__CalcMemUsed = async () => 4; + host.__size_of = async () => 8; + host.__Symbol_exists = async () => 1; + host.__Offset_of = async () => 16; + const ctx = new EvalContext({ data: host, container: base }); + + await expect(evalNode(parseExpression('__Running', false).ast, ctx)).resolves.toBe(1); + await expect(evalNode(parseExpression('__GetRegVal("r0")', false).ast as CallExpression, ctx)).resolves.toBe(7); + await expect(evalNode(parseExpression('__FindSymbol("x")', false).ast as CallExpression, ctx)).resolves.toBe(9); + await expect(evalNode(parseExpression('__CalcMemUsed(1,2,3,4)', false).ast as CallExpression, ctx)).resolves.toBe(4); + await expect(evalNode(parseExpression('__size_of("x")', false).ast as CallExpression, ctx)).resolves.toBe(8); + await expect(evalNode(parseExpression('__Symbol_exists("x")', false).ast as CallExpression, ctx)).resolves.toBe(1); + await expect(evalNode(parseExpression('__Offset_of("m")', false).ast as CallExpression, ctx)).resolves.toBe(16); + + const missingCtx = new EvalContext({ data: new BranchHost(new Map()), container: base }); + await expect(evalNode({ kind: 'EvalPointCall', intrinsic: '__CalcMemUsed', callee: { kind: 'Identifier', name: '__CalcMemUsed', start: 0, end: 0 } as Identifier, args: [], start: 0, end: 0 } as EvalPointCall, missingCtx)).rejects.toThrow('Intrinsic __CalcMemUsed expects at least 4 argument(s)'); + }); + + it('recovers containers via findReferenceNode across node kinds', async () => { + const base = new BranchNode('base'); + const vals = new Map([ + ['x', new BranchNode('x', base, 1)], + ['y', new BranchNode('y', base, 2)], + ['z', new BranchNode('z', base, 3)], + ['elem', new BranchNode('elem', base, 4)], + ['__Running', new BranchNode('__Running', base, 0)], + ]); + const arr = new BranchNode('arr', base, 0, { '0': vals.get('elem') as BranchNode }); + const obj = new BranchNode('obj', base, 0, { m: new BranchNode('m', base, 6) }); + vals.set('arr', arr); + vals.set('obj', obj); + const host = new BranchHost(vals); + host.resolveColonPath = async () => 0; + host.__Running = async () => 1; + const ctx = new EvalContext({ data: host, container: base }); + + const unaryAst = parseExpression('!x', false).ast as UnaryExpression; + const updateAst = parseExpression('++y', false).ast as UpdateExpression; + const binaryAst = parseExpression('x + y', false).ast as BinaryExpression; + const condAst = parseExpression('x ? y : z', false).ast as ConditionalExpression; + const assignAst = parseExpression('z = 5', false).ast as AssignmentExpression; + const evalCall: EvalPointCall = { kind: 'EvalPointCall', intrinsic: '__Running', callee: { kind: 'Identifier', name: '__Running', start: 0, end: 0 } as Identifier, args: [], start: 0, end: 0 }; + const memberAst = parseExpression('obj.m', false).ast as MemberAccess; + const arrayAst = parseExpression('arr[0]', false).ast as ArrayIndex; + const printfAst: PrintfExpression = { + kind: 'PrintfExpression', + segments: [ + { kind: 'TextSegment', text: 'pre', start: 0, end: 0 } as TextSegment, + segFromAst(parseExpression('x', false).ast), + ], + resultType: 'string', + start: 0, + end: 0, + }; + + const segments: FormatSegment[] = [ + segFromAst(unaryAst), + segFromAst(updateAst), + segFromAst(binaryAst), + segFromAst(condAst), + segFromAst(assignAst), + segFromAst(evalCall), + segFromAst(memberAst), + segFromAst(arrayAst), + ]; + + for (const seg of segments) { + await expect(evalNode(seg, ctx)).resolves.toBeDefined(); + } + + // PrintfExpression path with formatPrintf override + host.formatPrintf = async () => 'fmt'; + await expect(evalNode(printfAst, ctx)).resolves.toContain('fmt'); + }); + + it('resolves array member via fast path with stride and element refs', async () => { + const base = new BranchNode('base'); + const member = new BranchNode('m', base, 42); + const element = new BranchNode('elem', base, 0, { m: member }); + const arr = new BranchNode('arr', base, undefined, { '1': element }); + (arr as unknown as { getElementRef(): BranchNode }).getElementRef = () => element; + + const values = new Map([ + ['arr', arr], + ['elem', element], + ['m', member], + ]); + const host = new BranchHost(values); + host.getMemberOffset = async () => 4; + host.getElementStride = async () => 8; + host.getByteWidth = async () => 4; + + const ctx = new EvalContext({ data: host, container: base }); + await expect(evalNode(parseExpression('arr[1].m', false).ast, ctx)).resolves.toBe(42); + }); + + it('covers bigint comparisons and compound assignments', async () => { + const base = new BranchNode('base'); + const makeHost = (entries: [string, BranchNode][]) => new BranchHost(new Map(entries)); + + // Bigint comparisons exercise eq/lt/gte bigint paths and string equality coercion + const bigA = new BranchNode('bigA', base, 5n); + const bigB = new BranchNode('bigB', base, 7n); + const compareCtx = new EvalContext({ data: makeHost([['bigA', bigA], ['bigB', bigB]]), container: base }); + await expect(evalNode(parseExpression('bigA == bigA', false).ast, compareCtx)).resolves.toBeTruthy(); + await expect(evalNode(parseExpression('bigA < bigB', false).ast, compareCtx)).resolves.toBeTruthy(); + await expect(evalNode(parseExpression('bigB >= bigA', false).ast, compareCtx)).resolves.toBeTruthy(); + await expect(evalNode(parseExpression('"1" == 1', false).ast, compareCtx)).resolves.toBeTruthy(); + + // Compound assignment operators cover arithmetic/bitwise branches + const target = new BranchNode('c', base, 8); + const other = new BranchNode('d', base, 3); + const ctx = new EvalContext({ data: makeHost([['c', target], ['d', other]]), container: base }); + const run = async (expr: string, expected: number) => { + target.value = expr.startsWith('c %=') + ? 9 // ensure a value divisible by d for %= branch + : target.value; + await expect(evalNode(parseExpression(expr, false).ast, ctx)).resolves.toBe(expected); + }; + + target.value = 8; await run('c += d', 11); + target.value = 8; await run('c -= d', 5); + target.value = 8; await run('c *= d', 24); + target.value = 8; await run('c /= d', 2); + target.value = 9; await run('c %= d', 0); + target.value = 1; await run('c <<= d', 8); + target.value = 8; await run('c >>= d', 1); + target.value = 6; await run('c &= d', 2); + target.value = 6; await run('c ^= d', 5); + target.value = 6; await run('c |= d', 7); + }); + + it('normalizes scalar types that only provide a typename', async () => { + const base = new BranchNode('base'); + let sawTypename = false; + class TypenameHost extends BranchHost { + async getValueType(container: RefContainer): Promise { + const cur = container.current as BranchNode | undefined; + if (cur?.name === 'alias') { + sawTypename = true; + return { kind: 'int', bits: 16, typename: 'alias_t' }; + } + return super.getValueType(container); + } + } + + const host = new TypenameHost(new Map([['alias', new BranchNode('alias', base, 1)]])); + const ctx = new EvalContext({ data: host, container: base }); + await expect(evalNode(parseExpression('alias + 1', false).ast, ctx)).resolves.toBe(2); + expect(sawTypename).toBe(true); + }); + + it('recovers references across all findReferenceNode branches', async () => { + class ClearingHost extends Host { + async readValue(container: RefContainer): Promise { + const v = await super.readValue(container); + container.current = undefined; // force recovery path + return v; + } + } + + const base = new FakeNode('base'); + const fn = new FakeNode('fn', base, ((n: number) => n + 1) as unknown as EvalValue); + const val = new FakeNode('v', base, 1); + const values = new Map([['fn', fn], ['v', val], ['__Running', new FakeNode('__Running', base, 1)]]); + const host = new ClearingHost(values); + host.__Running = async () => 1; + + const ctx = new EvalContext({ data: host, container: base }); + + const assignmentSeg = segFromAst(parseExpression('v = 2', false).ast); + const callSeg = segFromAst(parseExpression('fn(3)', false).ast); + const evalPointSeg: FormatSegment = { + kind: 'FormatSegment', + spec: 'd', + value: { kind: 'EvalPointCall', intrinsic: '__Running', callee: { kind: 'Identifier', name: '__Running', start: 0, end: 0 } as Identifier, args: [], start: 0, end: 0 }, + start: 0, + end: 0, + }; + const printfSeg = segFromAst({ + kind: 'PrintfExpression', + segments: [{ kind: 'TextSegment', text: 'x', start: 0, end: 0 }, segFromAst(parseExpression('v', false).ast)], + resultType: 'string', + start: 0, + end: 0, + } as PrintfExpression); + const literalSeg = segFromAst({ kind: 'NumberLiteral', value: 9, raw: '9', valueType: 'number', constValue: 9, start: 0, end: 1 } as ASTNode); + + await expect(evalNode(assignmentSeg, ctx)).resolves.toBeDefined(); + await expect(evalNode(callSeg, ctx)).resolves.toBeDefined(); + await expect(evalNode(evalPointSeg, ctx)).resolves.toBeDefined(); + await expect(evalNode(printfSeg, ctx)).resolves.toBeDefined(); + await expect(evalNode(literalSeg, ctx)).resolves.toBeDefined(); + }); + + it('handles call expressions and read/write failures', async () => { + class FnHost extends Host { + async writeValue(): Promise { + return undefined; + } + } + class UndefinedReadHost extends Host { + async readValue(): Promise { + return undefined; + } + } + + const base = new FakeNode('base'); + const fnNode = new FakeNode('fn', base, ((a: number, b: number) => a + b) as unknown as EvalValue); + const valNode = new FakeNode('x', base, 1); + + const fnHost = new FnHost(new Map([['fn', fnNode], ['x', valNode]])); + const fnCtx = new EvalContext({ data: fnHost, container: base }); + await expect(evalNode(parseExpression('fn(2, 3)', false).ast, fnCtx)).resolves.toBe(5); + await expect(evalNode(parseExpression('x(1)', false).ast, fnCtx)).rejects.toThrow('Callee is not callable.'); + await expect(evalNode(parseExpression('x = 2', false).ast, fnCtx)).rejects.toThrow('Write returned undefined'); + + const readHost = new UndefinedReadHost(new Map([['x', valNode]])); + const readCtx = new EvalContext({ data: readHost, container: base }); + await expect(evalNode(parseExpression('x', false).ast, readCtx)).rejects.toThrow('Undefined value'); + }); + + it('formats values across printf specs and NaN paths', async () => { + const base = new BranchNode('base'); + const host = new BranchHost(new Map()); + const ctx = new EvalContext({ data: host, container: base }); + + const specs: Array<{ spec: FormatSegment['spec']; ast: ASTNode; expect: string }> = [ + { spec: '%', ast: { kind: 'NumberLiteral', value: 0, raw: '0', valueType: 'number', constValue: 0, start: 0, end: 1 }, expect: '%' }, + { spec: 'd', ast: { kind: 'NumberLiteral', value: Number.POSITIVE_INFINITY, raw: 'inf', valueType: 'number', constValue: undefined, start: 0, end: 1 }, expect: 'NaN' }, + { spec: 'u', ast: { kind: 'NumberLiteral', value: Number.NaN, raw: 'NaN', valueType: 'number', constValue: undefined, start: 0, end: 1 }, expect: 'NaN' }, + { spec: 'u', ast: { kind: 'NumberLiteral', value: -5, raw: '-5', valueType: 'number', constValue: -5, start: 0, end: 2 }, expect: String(((-5) >>> 0)) }, + { spec: 'x', ast: { kind: 'NumberLiteral', value: 255, raw: '255', valueType: 'number', constValue: 255, start: 0, end: 3 }, expect: 'ff' }, + { spec: 't', ast: { kind: 'BooleanLiteral', value: false, valueType: 'boolean', start: 0, end: 1 } as ASTNode, expect: 'false' }, + { spec: 'S', ast: { kind: 'StringLiteral', value: 'hi', raw: '"hi"', valueType: 'string', constValue: 'hi', start: 0, end: 2 } as ASTNode, expect: 'hi' }, + { spec: 'C', ast: { kind: 'StringLiteral', value: 'foo', raw: '"foo"', valueType: 'string', constValue: 'foo', start: 0, end: 3 } as ASTNode, expect: 'foo' }, + ]; + + for (const { spec, ast, expect: expected } of specs) { + const seg: FormatSegment = { kind: 'FormatSegment', spec, value: ast, start: 0, end: 0 }; + await expect(evalNode(seg, ctx)).resolves.toBe(expected); + } + }); +}); diff --git a/src/views/component-viewer/test/unit/parser-evaluator/intrinsics/intrinsics.test.ts b/src/views/component-viewer/test/unit/parser-evaluator/intrinsics/intrinsics.test.ts new file mode 100644 index 00000000..08deb956 --- /dev/null +++ b/src/views/component-viewer/test/unit/parser-evaluator/intrinsics/intrinsics.test.ts @@ -0,0 +1,139 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for Intrinsics. + */ + +import { handleIntrinsic, handlePseudoMember, INTRINSIC_DEFINITIONS, isIntrinsicName, type IntrinsicName, type IntrinsicProvider } from '../../../../parser-evaluator/intrinsics'; +import type { RefContainer } from '../../../../parser-evaluator/model-host'; +import { ScvdNode } from '../../../../model/scvd-node'; + +class TestNode extends ScvdNode { + constructor() { + super(undefined); + } +} + +describe('intrinsics', () => { + const base = new TestNode(); + const container = (): RefContainer => ({ base, valueType: undefined }); + + it('enforces intrinsic arg bounds', async () => { + const host = {} as unknown as IntrinsicProvider; + await expect(handleIntrinsic(host, container(), '__GetRegVal', [])).rejects.toThrow('at least 1 argument'); + await expect(handleIntrinsic(host, container(), '__CalcMemUsed', [1, 2, 3])).rejects.toThrow('at least 4 argument'); + await expect(handleIntrinsic(host, container(), '__CalcMemUsed', [1, 2, 3, 4, 5])).rejects.toThrow('at most 4 argument'); + }); + + it('runs numeric intrinsics and coercions', async () => { + const host = { + __GetRegVal: jest.fn(async (r: string) => r === 'r0' ? 1 : undefined), + __FindSymbol: jest.fn(async () => 0x10), + __CalcMemUsed: jest.fn(async (a: number, b: number, c: number, d: number) => a + b + c + d), + __size_of: jest.fn(async () => 4), + __Symbol_exists: jest.fn(async () => 1), + __Offset_of: jest.fn(async (_c: RefContainer, name: string) => name.length), + __Running: jest.fn(async () => 1), + } as unknown as IntrinsicProvider; + + await expect(handleIntrinsic(host, container(), '__GetRegVal', ['r0'])).resolves.toBe(1); + await expect(handleIntrinsic(host, container(), '__FindSymbol', ['main'])).resolves.toBe(0x10); + await expect(handleIntrinsic(host, container(), '__CalcMemUsed', [1, 2, 3, 4])).resolves.toBe(10); + await expect(handleIntrinsic(host, container(), '__size_of', ['T'])).resolves.toBe(4); + await expect(handleIntrinsic(host, container(), '__Symbol_exists', ['foo'])).resolves.toBe(1); + await expect(handleIntrinsic(host, container(), '__Offset_of', ['member'])).resolves.toBe('member'.length >>> 0); + await expect(handleIntrinsic(host, container(), '__Running', [])).resolves.toBe(1); + }); + + it('throws on missing or undefined intrinsic results', async () => { + const missing = {} as unknown as IntrinsicProvider; + await expect(handleIntrinsic(missing, container(), '__Running', [])).rejects.toThrow('Missing intrinsic __Running'); + await expect(handleIntrinsic(missing, container(), '__GetRegVal', ['r0'])).rejects.toThrow('Missing intrinsic __GetRegVal'); + await expect(handleIntrinsic(missing, container(), '__FindSymbol', ['x'])).rejects.toThrow('Missing intrinsic __FindSymbol'); + await expect(handleIntrinsic(missing, container(), '__CalcMemUsed', [0, 0, 0, 0])).rejects.toThrow('Missing intrinsic __CalcMemUsed'); + await expect(handleIntrinsic(missing, container(), '__size_of', ['x'])).rejects.toThrow('Missing intrinsic __size_of'); + await expect(handleIntrinsic(missing, container(), '__Symbol_exists', ['x'])).rejects.toThrow('Missing intrinsic __Symbol_exists'); + await expect(handleIntrinsic(missing, container(), '__Offset_of', ['m'])).rejects.toThrow('Missing intrinsic __Offset_of'); + + const host = { + __GetRegVal: jest.fn(async () => undefined), + __FindSymbol: jest.fn(async () => undefined), + __CalcMemUsed: jest.fn(async () => undefined), + __size_of: jest.fn(async () => undefined), + __Symbol_exists: jest.fn(async () => undefined), + __Offset_of: jest.fn(async () => undefined), + __Running: jest.fn(async () => undefined), + } as unknown as IntrinsicProvider; + + await expect(handleIntrinsic(host, container(), '__GetRegVal', ['r0'])).rejects.toThrow('returned undefined'); + await expect(handleIntrinsic(host, container(), '__FindSymbol', ['x'])).rejects.toThrow('returned undefined'); + await expect(handleIntrinsic(host, container(), '__CalcMemUsed', [0, 0, 0, 0])).rejects.toThrow('returned undefined'); + await expect(handleIntrinsic(host, container(), '__size_of', ['x'])).rejects.toThrow('returned undefined'); + await expect(handleIntrinsic(host, container(), '__Symbol_exists', ['x'])).rejects.toThrow('returned undefined'); + await expect(handleIntrinsic(host, container(), '__Offset_of', ['m'])).rejects.toThrow('returned undefined'); + await expect(handleIntrinsic(host, container(), '__Running', [])).rejects.toThrow('returned undefined'); + + await expect(handleIntrinsic(host, container(), '__Running' as IntrinsicName, [])).rejects.toThrow(); + await expect(handleIntrinsic(host, container(), '__GetRegVal' as IntrinsicName, [''])).rejects.toThrow(); + await expect(handleIntrinsic(host, container(), '__Symbol_exists' as IntrinsicName, [''])).rejects.toThrow(); + await expect(handleIntrinsic(host, container(), '__Offset_of' as IntrinsicName, [''])).rejects.toThrow(); + await expect(handleIntrinsic(host, container(), '__CalcMemUsed' as IntrinsicName, [0, 0, 0, 0])).rejects.toThrow(); + await expect(handleIntrinsic(host, container(), '__FindSymbol' as IntrinsicName, [''])).rejects.toThrow(); + await expect(handleIntrinsic(host, container(), '__size_of' as IntrinsicName, [''])).rejects.toThrow(); + + await expect(handleIntrinsic(host, container(), '_addr' as IntrinsicName, [])).rejects.toThrow('Missing intrinsic _addr'); + await expect(handleIntrinsic(host, container(), '__NotReal' as IntrinsicName, [])).rejects.toThrow('Missing intrinsic __NotReal'); + }); + + it('covers pseudo-member handling', async () => { + const host = { + _count: jest.fn(async () => 2), + _addr: jest.fn(async () => 0x1000), + } as unknown as IntrinsicProvider; + + await expect(handlePseudoMember(host, container(), '_count', base)).resolves.toBe(2); + await expect(handlePseudoMember(host, container(), '_addr', base)).resolves.toBe(0x1000); + + const missing = {} as unknown as IntrinsicProvider; + await expect(handlePseudoMember(missing, container(), '_count', base)).rejects.toThrow('Missing pseudo-member _count'); + + const undef = { + _count: jest.fn(async () => undefined), + _addr: jest.fn(async () => undefined), + } as unknown as IntrinsicProvider; + await expect(handlePseudoMember(undef, container(), '_addr', base)).rejects.toThrow('returned undefined'); + }); + + it('keeps intrinsic definitions aligned', () => { + expect(Object.keys(INTRINSIC_DEFINITIONS).sort()).toEqual([ + '__CalcMemUsed', + '__FindSymbol', + '__GetRegVal', + '__Offset_of', + '__Running', + '__Symbol_exists', + '__size_of', + '_addr', + '_count', + ].sort()); + + expect(isIntrinsicName('__Running')).toBe(true); + expect(isIntrinsicName('notAnIntrinsic')).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/parser-evaluator/parser/math-ops.test.ts b/src/views/component-viewer/test/unit/parser-evaluator/parser/math-ops.test.ts new file mode 100644 index 00000000..21987e20 --- /dev/null +++ b/src/views/component-viewer/test/unit/parser-evaluator/parser/math-ops.test.ts @@ -0,0 +1,103 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit tests for shared math helpers used by parser and evaluator. + */ + +import { + addVals, + andVals, + divVals, + maskToBits, + mergeKinds, + modVals, + mulVals, + normalizeToWidth, + orVals, + sarVals, + shlVals, + shrVals, + subVals, + toBigInt, + toNumeric, + xorVals, +} from '../../../../parser-evaluator/math-ops'; + +describe('math-ops helpers', () => { + it('handles numeric coercion and string concat', () => { + expect(addVals(1, 2)).toBe(3); + expect(addVals('a', 2)).toBe('a2'); + expect(toNumeric(true)).toBe(1); + expect(toNumeric('10')).toBe(10); + expect(toNumeric(' ')).toBe(0); + expect(toNumeric('bad')).toBe(0); + expect(toBigInt('not-a-number' as unknown as string)).toBe(0n); + expect(toBigInt(true)).toBe(1n); + expect(toBigInt(3.7)).toBe(3n); + expect(toBigInt(undefined)).toBe(0n); + }); + + it('masks and normalizes widths for signed and unsigned', () => { + expect(maskToBits(0xFF, 4)).toBe(0xF); + expect(maskToBits(0xFFn, 4)).toBe(0xFn); + expect(normalizeToWidth(0xFF, 8, 'uint')).toBe(0xFF); + expect(normalizeToWidth(0xFF, 8, 'int')).toBe(-1); + expect(normalizeToWidth(0x1FFn, 8, 'int')).toBe(-1n); + expect(maskToBits(0x1234_5678, 40)).toBe(0x1234_5678); // >=32 path + expect(maskToBits(0xABCD, 0)).toBe(0xABCD); // no-op when bits falsy + expect(normalizeToWidth(5, undefined, 'float')).toBe(5); // float bypass + expect(normalizeToWidth(-1, 33, 'int')).toBe(-1); // width capped at 32 + }); + + it('performs integer arithmetic with optional truncation', () => { + expect(subVals(10, 3)).toBe(7); + expect(mulVals(4, 3)).toBe(12); + expect(modVals(10, 3)).toBe(1); + expect(addVals(255, 2, 8, true)).toBe(1); // wraps to 8 bits + expect(addVals(0x1FFn, 2n, 8, true)).toBe(1n); // bigint mask path + expect(subVals(1n, 3n)).toBe(-2n); + expect(mulVals(2n, 3n, 4, true)).toBe(6n); + expect(modVals(10n, 3n)).toBe(1n); + expect(() => divVals(1, 0)).toThrow('Division by zero'); + expect(divVals(5n, 2n)).toBe(2n); + expect(divVals(6, 2)).toBe(3); + }); + + it('supports bitwise ops and shifts across kinds', () => { + expect(andVals(0xF0, 0x0F)).toBe(0); + expect(orVals(0xF0, 0x0F)).toBe(0xFF); + expect(xorVals(0xAA, 0xFF)).toBe(0x55); + expect(shlVals(1, 3, 8, true)).toBe(8); + expect(sarVals(-16, 2)).toBe(0xFFFF_FFFC); // current helper masks to unsigned for number path + expect(shrVals(-1, 1, 8)).toBe(0xFF); + expect(andVals(0xF0n, 0x0Fn)).toBe(0n); + expect(orVals(0xF0n, 0x0Fn)).toBe(0xFFn); + expect(xorVals(0xAAAn, 0xFFn)).toBe(0xA55n); + expect(shlVals(1n, 65n, 8, true)).toBe(0n); // bigint shift with mask + expect(sarVals(8n, 1n)).toBe(4n); + expect(shrVals(-1n, 1n, 8)).toBe(0n); + }); + + it('merges scalar kinds with float > uint > int precedence', () => { + expect(mergeKinds({ kind: 'int' }, { kind: 'uint' })).toBe('uint'); + expect(mergeKinds({ kind: 'uint' }, { kind: 'float' })).toBe('float'); + expect(mergeKinds({ kind: 'int' }, { kind: 'int' })).toBe('int'); + expect(mergeKinds(undefined, undefined)).toBe('unknown'); + }); +}); diff --git a/src/views/component-viewer/test/unit/resolver.test.ts b/src/views/component-viewer/test/unit/resolver.test.ts new file mode 100644 index 00000000..ceb9f2e5 --- /dev/null +++ b/src/views/component-viewer/test/unit/resolver.test.ts @@ -0,0 +1,104 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for resolver: cache creation, dispatching, and guard branches. + */ + +import { Resolver, ResolveType } from '../../resolver'; +import { ScvdComponentViewer } from '../../model/scvd-component-viewer'; +import { ScvdTypedef, ScvdTypedefs } from '../../model/scvd-typedef'; +import { ScvdNode } from '../../model/scvd-node'; + +class FakeNode extends ScvdNode { + public calls = 0; + public override resolveAndLink(): boolean { + this.calls += 1; + return true; + } +} + +class FakeViewer extends ScvdComponentViewer { + public calls = 0; + public override resolveAndLink(): boolean { + this.calls += 1; + return true; + } +} + +describe('Resolver', () => { + it('creates type cache and resolves recursively over children', () => { + const viewer = new FakeViewer(undefined); + const child = new FakeNode(viewer); + const grandChild = new FakeNode(child); + const typedefs = new ScvdTypedefs(viewer); + const td = typedefs.addTypedef(); + td.name = 'T1'; + (viewer as unknown as { _typedefs?: ScvdTypedefs })._typedefs = typedefs; + + const resolver = new Resolver(viewer); + expect(resolver.resolve()).toBe(true); + expect(resolver.typesCache?.findTypeByName('T1')).toBe(td); + expect(viewer.calls).toBe(1); + expect(child.calls).toBe(1); + expect(grandChild.calls).toBe(1); + }); + + it('routes resolveSymbolCb based on resolve type', () => { + const viewer = new FakeViewer(undefined); + const resolver = new Resolver(viewer); + jest.spyOn(console, 'log').mockImplementation(() => {}); + const typedef = new ScvdTypedef(viewer); + typedef.name = 'Found'; + (resolver as unknown as { _typesCache: { findTypeByName: (n: string) => ScvdTypedef | undefined } })._typesCache = { + findTypeByName: (n: string) => (n === 'Found' ? typedef : undefined), + }; + const member = new FakeNode(undefined); + const scvdObject = { getSymbol: (n: string) => (n === 'mem' ? member : undefined) } as unknown as ScvdNode; + + expect(resolver.resolveSymbolCb('Found', ResolveType.localType)).toBe(typedef); + expect(resolver.resolveSymbolCb('mem', ResolveType.localMember, scvdObject)).toBe(member); + const scvdObjectMissing = { getSymbol: () => undefined } as unknown as ScvdNode; + expect(resolver.resolveSymbolCb('mem', ResolveType.localMember, scvdObjectMissing)).toBeUndefined(); + expect(resolver.resolveSymbolCb('any', ResolveType.targetType)).toBeUndefined(); + expect(resolver.resolveSymbolCb('x', ResolveType.localMember)).toBeUndefined(); // scvdObject undefined branch + // Local type missing / non-typedef branch + (resolver as unknown as { _typesCache: { findTypeByName: (n: string) => ScvdNode | undefined } })._typesCache = { + findTypeByName: () => new FakeNode(undefined), + }; + expect(resolver.resolveSymbolCb('Other', ResolveType.localType)).toBeUndefined(); + // Default switch branch + expect(resolver.resolveSymbolCb('noop', 'bogus' as unknown as ResolveType)).toBeUndefined(); + (console.log as unknown as jest.Mock).mockRestore(); + }); + + it('guards when model or caches are missing', () => { + const viewer = new FakeViewer(undefined); + const resolver = new Resolver(viewer); + + (resolver as unknown as { _model?: undefined })._model = undefined; + expect(resolver.resolve()).toBe(false); + + (resolver as unknown as { _model?: FakeViewer })._model = viewer; + (resolver as unknown as { _typesCache?: undefined })._typesCache = undefined; + expect((resolver as unknown as { resolveTypes: () => boolean }).resolveTypes()).toBe(false); + + (resolver as unknown as { _model?: undefined })._model = undefined; + expect((resolver as unknown as { createTypesCache: () => void }).createTypesCache()).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/scvd-eval-context.test.ts b/src/views/component-viewer/test/unit/scvd-eval-context.test.ts new file mode 100644 index 00000000..f795e7db --- /dev/null +++ b/src/views/component-viewer/test/unit/scvd-eval-context.test.ts @@ -0,0 +1,102 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdEvalContext happy-path and failure branches. + */ + +import { ScvdEvalContext } from '../../scvd-eval-context'; +import { ScvdComponentViewer } from '../../model/scvd-component-viewer'; +import { ScvdObjects } from '../../model/scvd-object'; +import type { ScvdNode } from '../../model/scvd-node'; +import { MemoryHost } from '../../data-host/memory-host'; +import { RegisterHost } from '../../data-host/register-host'; + +describe('ScvdEvalContext', () => { + const buildViewerWithObject = (): { viewer: ScvdComponentViewer; firstObject: ScvdNode } => { + const viewer = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(viewer); + const first = objects.addObject(); + (viewer as unknown as { _objects?: ScvdObjects })._objects = objects; + return { viewer, firstObject: first }; + }; + + it('constructs with an out item and returns execution context', () => { + const { viewer, firstObject } = buildViewerWithObject(); + const ctx = new ScvdEvalContext(viewer); + expect(ctx.getOutItem()).toBe(firstObject); + + const exec = ctx.getExecutionContext(); + expect(exec.memoryHost).toBeDefined(); + expect(exec.registerHost).toBeDefined(); + expect(exec.evalContext).toBeDefined(); + expect(exec.debugTarget).toBeDefined(); + }); + + it('throws when no output item exists', () => { + const viewer = new ScvdComponentViewer(undefined); + expect(() => new ScvdEvalContext(viewer)).toThrow('SCVD EvalContext: No output item defined'); + }); + + it('throws when model or hosts are unset internally', () => { + const { viewer } = buildViewerWithObject(); + const ctx = new ScvdEvalContext(viewer); + + (ctx as unknown as { _model: ScvdComponentViewer | undefined })._model = undefined; + expect(() => ctx.getOutItem()).toThrow('SCVD EvalContext: Model not initialized'); + + (ctx as unknown as { _memoryHost: MemoryHost | undefined })._memoryHost = undefined; + expect(() => ctx.getExecutionContext()).toThrow('SCVD EvalContext: MemoryHost not initialized'); + + // RegisterHost guard + (ctx as unknown as { _memoryHost: MemoryHost })._memoryHost = new ScvdEvalContext(viewer)['getExecutionContext']().memoryHost; + (ctx as unknown as { _registerHost: RegisterHost | undefined })._registerHost = undefined; + expect(() => ctx.getExecutionContext()).toThrow('SCVD EvalContext: RegisterHost not initialized'); + + // EvalContext guard + (ctx as unknown as { _registerHost: RegisterHost })._registerHost = new ScvdEvalContext(viewer)['getExecutionContext']().registerHost; + (ctx as unknown as { _ctx: ReturnType['evalContext'] | undefined })._ctx = undefined; + expect(() => ctx.getExecutionContext()).toThrow('SCVD EvalContext: EvalContext not initialized'); + + // DebugTarget guard + (ctx as unknown as { _ctx: ReturnType['evalContext'] })._ctx = new ScvdEvalContext(viewer)['getExecutionContext']().evalContext; + (ctx as unknown as { _debugTarget: ReturnType['debugTarget'] | undefined })._debugTarget = undefined; + expect(() => ctx.getExecutionContext()).toThrow('SCVD EvalContext: DebugTarget not initialized'); + }); + + it('returns undefined when objects container is empty', () => { + const { viewer } = buildViewerWithObject(); + const ctx = new ScvdEvalContext(viewer); + // Replace objects with an empty container after construction + const emptyObjects = new ScvdObjects(viewer); + (emptyObjects as unknown as { _objects: unknown[] })._objects = []; + (viewer as unknown as { _objects?: ScvdObjects })._objects = emptyObjects; + expect(ctx.getOutItem()).toBeUndefined(); + }); + + it('delegates init to debug target', () => { + const { viewer } = buildViewerWithObject(); + const ctx = new ScvdEvalContext(viewer); + const debugTarget = ctx.getExecutionContext().debugTarget; + jest.spyOn(debugTarget, 'init').mockImplementation(() => undefined as unknown as void); + const fakeSession = {} as unknown as Parameters[0]; + const fakeTracker = {} as unknown as Parameters[1]; + ctx.init(fakeSession, fakeTracker); + expect(debugTarget.init).toHaveBeenCalledWith(fakeSession, fakeTracker); + }); +}); diff --git a/src/views/component-viewer/test/unit/scvd-types-cache.test.ts b/src/views/component-viewer/test/unit/scvd-types-cache.test.ts new file mode 100644 index 00000000..fcd35d23 --- /dev/null +++ b/src/views/component-viewer/test/unit/scvd-types-cache.test.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for ScvdTypesCache creation and lookup paths. + */ + +import { ScvdTypesCache } from '../../scvd-types-cache'; +import { ScvdComponentViewer } from '../../model/scvd-component-viewer'; +import { ScvdTypedefs } from '../../model/scvd-typedef'; +import { ScvdNode } from '../../model/scvd-node'; + +class MinimalNode extends ScvdNode { + constructor(parent?: ScvdNode) { + super(parent); + } + public override async getValue(): Promise { + return undefined; + } +} + +describe('ScvdTypesCache', () => { + it('returns undefined when no model or typedefs are present', () => { + const viewer = new ScvdComponentViewer(undefined); + const cache = new ScvdTypesCache(viewer); + + cache.createCache(); // with no typedefs set, should no-op + expect(cache.findTypeByName('missing')).toBeUndefined(); + + // Simulate model cleared after construction + (cache as unknown as { _model: ScvdComponentViewer | undefined })._model = undefined; + cache.createCache(); + expect(cache.findTypeByName('anything')).toBeUndefined(); + }); + + it('caches only ScvdTypedef entries and resolves by name', () => { + const viewer = new ScvdComponentViewer(undefined); + const typedefs = new ScvdTypedefs(viewer); + const good = typedefs.addTypedef(); + good.name = 'GoodType'; + // Insert a non-typedef child to ensure it is skipped + new MinimalNode(typedefs); + (viewer as unknown as { _typedefs: ScvdTypedefs | undefined })._typedefs = typedefs; + + const cache = new ScvdTypesCache(viewer); + cache.createCache(); + expect(cache.findTypeByName('GoodType')).toBe(good); + expect(cache.findTypeByName('Missing')).toBeUndefined(); + }); + + it('findTypeByName ignores non-typedef values in the map', () => { + const viewer = new ScvdComponentViewer(undefined); + const cache = new ScvdTypesCache(viewer); + (cache as unknown as { typesCache: Map | undefined }).typesCache = new Map([ + ['bad', new MinimalNode(undefined)], + ]); + expect(cache.findTypeByName('bad')).toBeUndefined(); + + // If the internal map is missing entirely, bail out gracefully + (cache as unknown as { typesCache: Map | undefined }).typesCache = undefined; + expect(cache.findTypeByName('anything')).toBeUndefined(); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-base.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-base.test.ts new file mode 100644 index 00000000..2130b730 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-base.test.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementBase. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { StatementBase } from '../../../statement-engine/statement-base'; +import { TestNode, createExecutionContext } from '../helpers/statement-engine-helpers'; + +class TestStatement extends StatementBase { + public executed = false; + + public createGuiChild(guiTree: ScvdGuiTree, guiName?: string, nodeId?: string): ScvdGuiTree { + return this.getOrCreateGuiChild(guiTree, guiName, nodeId); + } + + protected override async onExecute(): Promise { + this.executed = true; + } +} + +describe('StatementBase', () => { + it('sorts children by line with stable ordering', () => { + const rootNode = new TestNode(undefined); + const root = new TestStatement(rootNode, undefined); + + const childA = new TestNode(rootNode); + childA.lineNo = '2'; + const childB = new TestNode(rootNode); + childB.lineNo = '1'; + const childC = new TestNode(rootNode); + childC.lineNo = '1'; + + const stmtA = new TestStatement(childA, root); + const stmtB = new TestStatement(childB, root); + const stmtC = new TestStatement(childC, root); + + root.sortChildren(); + + expect(root.children).toEqual([stmtB, stmtC, stmtA]); + }); + + it('handles NaN line numbers as zero', () => { + const node = new TestNode(undefined); + node.lineNo = 'not-a-number'; + const stmt = new TestStatement(node, undefined); + + expect(stmt.line).toBe(0); + }); + + it('exposes parent statements', () => { + const rootNode = new TestNode(undefined); + const root = new TestStatement(rootNode, undefined); + const childNode = new TestNode(undefined); + const child = new TestStatement(childNode, root); + + expect(child.parent).toBe(root); + }); + + it('handles undefined children safely', () => { + const node = new TestNode(undefined); + const stmt = new TestStatement(node, undefined); + + const result = stmt.addChild(undefined as unknown as TestStatement); + + expect(result).toBeUndefined(); + expect(stmt.children).toHaveLength(0); + }); + + it('creates GUI children with fallback names', () => { + const node = new TestNode(undefined); + node.lineNo = '3'; + const stmt = new TestStatement(node, undefined); + const guiTree = new ScvdGuiTree(undefined); + guiTree.beginUpdate(); + + const child = stmt.createGuiChild(guiTree, undefined, 'node'); + + expect(child.key).toContain('Unnamed:TestNode:3'); + expect(child.nodeId).toContain('node_'); + }); + + it('executes statements when condition passes', async () => { + const node = new TestNode(undefined, { conditionResult: true }); + const stmt = new TestStatement(node, undefined); + const childNode = new TestNode(undefined, { conditionResult: true }); + const child = new TestStatement(childNode, stmt); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(stmt.executed).toBe(true); + expect(child.executed).toBe(true); + }); + + it('skips execution when condition is false', async () => { + const node = new TestNode(undefined, { conditionResult: false }); + const stmt = new TestStatement(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(stmt.executed).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-break.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-break.test.ts new file mode 100644 index 00000000..32ae8d30 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-break.test.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementBreak. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdBreak } from '../../../model/scvd-break'; +import { StatementBreak } from '../../../statement-engine/statement-break'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +describe('StatementBreak', () => { + it('skips execution when condition is false', async () => { + const node = new TestNode(undefined, { conditionResult: false }); + const stmt = new StatementBreak(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('logs when cast to break fails', async () => { + const node = new TestNode(undefined); + const stmt = new StatementBreak(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles valid break items', async () => { + const node = new ScvdBreak(undefined); + const stmt = new StatementBreak(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-calc.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-calc.test.ts new file mode 100644 index 00000000..9bd07fcf --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-calc.test.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementCalc. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdCalc } from '../../../model/scvd-calc'; +import { StatementCalc } from '../../../statement-engine/statement-calc'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +describe('StatementCalc', () => { + it('logs when cast to calc fails', async () => { + const node = new TestNode(undefined); + const stmt = new StatementCalc(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('evaluates calc expressions', async () => { + const calc = new ScvdCalc(undefined); + const exprA = { evaluate: jest.fn().mockResolvedValue(1) }; + const exprB = { evaluate: jest.fn().mockResolvedValue(2) }; + (calc as unknown as { _expression: Array<{ evaluate: () => Promise }> })._expression = [ + exprA, + exprB, + ]; + + const stmt = new StatementCalc(calc, undefined); + const ctx = createExecutionContext(calc); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(exprA.evaluate).toHaveBeenCalled(); + expect(exprB.evaluate).toHaveBeenCalled(); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-engine.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-engine.test.ts new file mode 100644 index 00000000..8fd69f0e --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-engine.test.ts @@ -0,0 +1,258 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementEngine. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { StatementEngine } from '../../../statement-engine/statement-engine'; +import { StatementBreak } from '../../../statement-engine/statement-break'; +import { StatementCalc } from '../../../statement-engine/statement-calc'; +import { StatementItem } from '../../../statement-engine/statement-item'; +import { StatementList } from '../../../statement-engine/statement-list'; +import { StatementListOut } from '../../../statement-engine/statement-list-out'; +import { StatementObject } from '../../../statement-engine/statement-object'; +import { StatementOut } from '../../../statement-engine/statement-out'; +import { StatementPrint } from '../../../statement-engine/statement-print'; +import { StatementRead } from '../../../statement-engine/statement-read'; +import { StatementReadList } from '../../../statement-engine/statement-readList'; +import { StatementVar } from '../../../statement-engine/statement-var'; +import { ScvdCalc } from '../../../model/scvd-calc'; +import { ScvdComponentViewer } from '../../../model/scvd-component-viewer'; +import { ScvdItem } from '../../../model/scvd-item'; +import { ScvdList } from '../../../model/scvd-list'; +import { ScvdListOut } from '../../../model/scvd-list-out'; +import { ScvdNode } from '../../../model/scvd-node'; +import { ScvdObject, ScvdObjects } from '../../../model/scvd-object'; +import { ScvdOut } from '../../../model/scvd-out'; +import { ScvdPrint } from '../../../model/scvd-print'; +import { ScvdRead } from '../../../model/scvd-read'; +import { ScvdReadList } from '../../../model/scvd-readlist'; +import { ScvdVar } from '../../../model/scvd-var'; +import { ScvdBreak, ScvdBreaks } from '../../../model/scvd-break'; +import { StatementBase } from '../../../statement-engine/statement-base'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +class FakeStatement extends StatementBase { + public calls = 0; + protected override async onExecute(): Promise { + this.calls += 1; + } +} + +type StatementCtor = new (item: ScvdNode, parent: StatementBase | undefined) => StatementBase; + +describe('StatementEngine', () => { + it('exposes model and execution context', () => { + const model = new ScvdComponentViewer(undefined); + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.model).toBe(model); + expect(engine.executionContext).toBe(ctx); + }); + + it('builds statement types for known nodes', () => { + const model = new ScvdComponentViewer(undefined); + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + const entries: Array<[ScvdNode, StatementCtor]> = [ + [new ScvdObject(undefined), StatementObject], + [new ScvdVar(undefined), StatementVar], + [new ScvdCalc(undefined), StatementCalc], + [new ScvdReadList(undefined), StatementReadList], + [new ScvdRead(undefined), StatementRead], + [new ScvdList(undefined), StatementList], + [new ScvdListOut(undefined), StatementListOut], + [new ScvdOut(undefined), StatementOut], + [new ScvdItem(undefined), StatementItem], + [new ScvdPrint(undefined), StatementPrint], + [new ScvdBreak(undefined), StatementBreak], + ]; + + for (const [node, ctor] of entries) { + const stmt = engine.addChildrenFromScvd(node, undefined); + expect(stmt).toBeInstanceOf(ctor); + } + }); + + it('returns undefined for unknown statement types', () => { + const model = new ScvdComponentViewer(undefined); + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + const unknown = new TestNode(undefined); + + const stmt = engine.addChildrenFromScvd(unknown, undefined); + + expect(stmt).toBeUndefined(); + }); + + it('returns false when there are no objects', () => { + const model = new ScvdComponentViewer(undefined); + (model as unknown as { _objects: ScvdObjects })._objects = new ScvdObjects(model); + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(false); + }); + + it('returns false when the first object is undefined', () => { + const model = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(model); + (objects as unknown as { _objects: Array })._objects = [undefined]; + (model as unknown as { _objects: ScvdObjects })._objects = objects; + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(false); + }); + + it('skips statement tree setup when no statement is built', () => { + const model = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(model); + (objects as unknown as { _objects: Array })._objects = [new TestNode(undefined)]; + (model as unknown as { _objects: ScvdObjects })._objects = objects; + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(true); + expect(engine.statementTree).toBeUndefined(); + }); + + it('initializes without break entries', () => { + const model = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(model); + const object = objects.addObject(); + object.lineNo = '1'; + (model as unknown as { _objects: ScvdObjects })._objects = objects; + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(true); + expect(engine.statementTree).toBeDefined(); + }); + + it('initializes and inserts breakpoints', () => { + const model = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(model); + const object = objects.addObject(); + object.lineNo = '1'; + const varA = object.addVar(); + varA.lineNo = '1'; + const varB = object.addVar(); + varB.lineNo = '5'; + (model as unknown as { _objects: ScvdObjects })._objects = objects; + + const breaks = new ScvdBreaks(model); + const breakA = breaks.addBreak(); + breakA.lineNo = '3'; + const breakB = breaks.addBreak(); + breakB.lineNo = '3'; + (model as unknown as { _breaks: ScvdBreaks })._breaks = breaks; + + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(true); + expect(engine.statementTree).toBeDefined(); + + const tree = engine.statementTree as StatementBase; + const breakNodes = tree.children.filter(child => child.scvdItem.constructor?.name === 'ScvdBreak'); + expect(breakNodes.length).toBe(1); + }); + + it('inserts breaks using nested spans', () => { + const model = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(model); + const object = objects.addObject(); + object.lineNo = '1'; + const list = object.addList(); + list.lineNo = '2'; + const item = list.addVar(); + item.lineNo = '5'; + (model as unknown as { _objects: ScvdObjects })._objects = objects; + + const breaks = new ScvdBreaks(model); + const breakItem = breaks.addBreak(); + breakItem.lineNo = '4'; + (model as unknown as { _breaks: ScvdBreaks })._breaks = breaks; + + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(true); + + const tree = engine.statementTree as StatementBase; + const listStmt = tree.children.find(child => child.scvdItem.constructor?.name === 'ScvdList') as StatementBase | undefined; + expect(listStmt?.children.some(child => child.scvdItem.constructor?.name === 'ScvdBreak')).toBe(true); + }); + + it('handles break span checks with descending line numbers', () => { + const model = new ScvdComponentViewer(undefined); + const objects = new ScvdObjects(model); + const object = objects.addObject(); + object.lineNo = '10'; + const list = object.addList(); + list.lineNo = '5'; + const varItem = list.addVar(); + varItem.lineNo = '4'; + (model as unknown as { _objects: ScvdObjects })._objects = objects; + + const breaks = new ScvdBreaks(model); + const breakItem = breaks.addBreak(); + breakItem.lineNo = '7'; + (model as unknown as { _breaks: ScvdBreaks })._breaks = breaks; + + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + + expect(engine.initialize()).toBe(true); + expect(engine.statementTree).toBeDefined(); + }); + + it('executes all statements and clears memory', async () => { + const model = new ScvdComponentViewer(undefined); + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + const rootNode = new TestNode(undefined); + const stmt = new FakeStatement(rootNode, undefined); + (engine as unknown as { _statementTree: StatementBase })._statementTree = stmt; + + const clearSpy = jest.spyOn(ctx.memoryHost, 'clear'); + const guiTree = new ScvdGuiTree(undefined); + + await engine.executeAll(guiTree); + + expect(clearSpy).toHaveBeenCalled(); + expect(stmt.calls).toBe(1); + }); + + it('executes without a statement tree', async () => { + const model = new ScvdComponentViewer(undefined); + const ctx = createExecutionContext(model); + const engine = new StatementEngine(model, ctx); + const clearSpy = jest.spyOn(ctx.memoryHost, 'clear'); + const guiTree = new ScvdGuiTree(undefined); + + await engine.executeAll(guiTree); + + expect(clearSpy).toHaveBeenCalled(); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-item.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-item.test.ts new file mode 100644 index 00000000..c9a21e28 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-item.test.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementItem. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { StatementItem } from '../../../statement-engine/statement-item'; +import { StatementOut } from '../../../statement-engine/statement-out'; +import { StatementPrint } from '../../../statement-engine/statement-print'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +function getOnlyChild(tree: ScvdGuiTree): ScvdGuiTree { + const child = tree.children[0]; + if (!child) { + throw new Error('Expected child to exist'); + } + return child; +} + +describe('StatementItem', () => { + it('skips execution when condition is false', async () => { + const node = new TestNode(undefined, { conditionResult: false }); + const stmt = new StatementItem(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('creates GUI entries for named items', async () => { + const node = new TestNode(undefined, { guiName: 'Item', guiValue: '42' }); + const stmt = new StatementItem(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + const child = getOnlyChild(guiTree); + expect(child.getGuiName()).toBe('Item'); + expect(child.getGuiValue()).toBe('42'); + }); + + it('uses print children when gui name is missing and keeps non-print children', async () => { + const node = new TestNode(undefined); + const stmt = new StatementItem(node, undefined); + + const printNode = new TestNode(node, { guiName: 'PrintName', guiValue: 'PrintValue' }); + new StatementPrint(printNode, stmt); + + const outNode = new TestNode(node, { guiName: 'OutChild' }); + new StatementOut(outNode, stmt); + + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + const child = getOnlyChild(guiTree); + expect(child.getGuiName()).toBe('PrintName'); + expect(child.getGuiValue()).toBe('PrintValue'); + expect(child.children.every(guiChild => !guiChild.isPrint)).toBe(true); + expect(child.children.length).toBe(1); + }); + + it('detaches empty items without gui name', async () => { + const node = new TestNode(undefined); + const stmt = new StatementItem(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('uses print-only children before detaching', async () => { + const node = new TestNode(undefined); + const stmt = new StatementItem(node, undefined); + const printNode = new TestNode(node, { guiName: 'PrintName', guiValue: 'PrintValue' }); + new StatementPrint(printNode, stmt); + + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('checks non-print children before selecting a print entry', async () => { + const node = new TestNode(undefined); + const stmt = new StatementItem(node, undefined); + const outNode = new TestNode(node, { guiName: 'OutChild' }); + new StatementOut(outNode, stmt); + const printNode = new TestNode(node, { guiName: 'PrintName', guiValue: 'PrintValue' }); + new StatementPrint(printNode, stmt); + + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(1); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-list-out.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-list-out.test.ts new file mode 100644 index 00000000..fb9cbf00 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-list-out.test.ts @@ -0,0 +1,370 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementListOut. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdListOut } from '../../../model/scvd-list-out'; +import { ScvdVar } from '../../../model/scvd-var'; +import { ScvdNode } from '../../../model/scvd-node'; +import { StatementBase } from '../../../statement-engine/statement-base'; +import { StatementListOut } from '../../../statement-engine/statement-list-out'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +class BaseContainer extends ScvdNode { + private symbols = new Map(); + + public addSymbol(name: string, node: ScvdNode): void { + this.symbols.set(name, node); + } + + public override getSymbol(name: string): ScvdNode | undefined { + return this.symbols.get(name); + } +} + +class CountingStatement extends StatementBase { + public executed = 0; + + protected override async onExecute(): Promise { + this.executed += 1; + } +} + +describe('StatementListOut', () => { + it('skips when condition is false', async () => { + const list = new ScvdListOut(undefined); + jest.spyOn(list, 'getConditionResult').mockResolvedValue(false); + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('logs when cast fails', async () => { + const node = new TestNode(undefined); + const stmt = new StatementListOut(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a name', async () => { + const list = new ScvdListOut(undefined); + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a start expression', async () => { + const list = new ScvdListOut(undefined); + list.name = 'loop'; + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a start value', async () => { + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(undefined); + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a base container', async () => { + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(list); + (ctx.evalContext.container as { base: ScvdNode | undefined }).base = undefined; + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a loop variable in the base container', async () => { + const base = new BaseContainer(undefined); + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires target size for the loop variable', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(undefined); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('rejects simultaneous limit and while expressions', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.limit = 'limit'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + jest.spyOn(list.limit!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValue(1); + + const stmt = new StatementListOut(list, undefined); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('breaks out when while start value is zero', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('executes while loops and updates loop values', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValueOnce(1).mockResolvedValueOnce(0); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(1); + }); + + it('breaks when while expression resolves to zero', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValue(0); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('breaks when while expression is undefined', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValue(undefined); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('handles undefined while updates after executing children', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValueOnce(1).mockResolvedValueOnce(undefined); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(1); + }); + + it('handles undefined limit values', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.limit = 'limit'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + jest.spyOn(list.limit!, 'getValue').mockResolvedValue(undefined); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('executes limit loops and increments', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdListOut(undefined); + list.name = 'loop'; + list.start = 'start'; + list.limit = 'limit'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + jest.spyOn(list.limit!, 'getValue').mockResolvedValue(2); + + const stmt = new StatementListOut(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(2); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-list.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-list.test.ts new file mode 100644 index 00000000..550068de --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-list.test.ts @@ -0,0 +1,300 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementList. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdList } from '../../../model/scvd-list'; +import { ScvdVar } from '../../../model/scvd-var'; +import { ScvdNode } from '../../../model/scvd-node'; +import { StatementBase } from '../../../statement-engine/statement-base'; +import { StatementList } from '../../../statement-engine/statement-list'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +class BaseContainer extends ScvdNode { + private symbols = new Map(); + + public addSymbol(name: string, node: ScvdNode): void { + this.symbols.set(name, node); + } + + public override getSymbol(name: string): ScvdNode | undefined { + return this.symbols.get(name); + } +} + +class CountingStatement extends StatementBase { + public executed = 0; + + protected override async onExecute(): Promise { + this.executed += 1; + } +} + +describe('StatementList', () => { + it('skips when condition is false', async () => { + const list = new ScvdList(undefined); + jest.spyOn(list, 'getConditionResult').mockResolvedValue(false); + const stmt = new StatementList(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('logs when cast fails', async () => { + const node = new TestNode(undefined); + const stmt = new StatementList(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a name', async () => { + const list = new ScvdList(undefined); + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a start expression', async () => { + const list = new ScvdList(undefined); + list.name = 'loop'; + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a start value', async () => { + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(undefined); + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(list); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a base container', async () => { + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(list); + (ctx.evalContext.container as { base: ScvdNode | undefined }).base = undefined; + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a loop variable in the base container', async () => { + const base = new BaseContainer(undefined); + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires target size for the loop variable', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(undefined); + base.addSymbol('loop', variable); + + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('rejects simultaneous limit and while expressions', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + list.limit = 'limit'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + jest.spyOn(list.limit!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValue(1); + + const stmt = new StatementList(list, undefined); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('executes while loops and children', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValueOnce(1).mockResolvedValueOnce(0); + + const stmt = new StatementList(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(1); + }); + + it('breaks when while expression is undefined', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + list.while = 'while'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(1); + jest.spyOn(list.while!, 'getValue').mockResolvedValue(undefined); + + const stmt = new StatementList(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('handles undefined limit values', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + list.limit = 'limit'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + jest.spyOn(list.limit!, 'getValue').mockResolvedValue(undefined); + + const stmt = new StatementList(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(0); + }); + + it('executes limit loops and increments', async () => { + const base = new BaseContainer(undefined); + const variable = new ScvdVar(base); + variable.name = 'loop'; + jest.spyOn(variable, 'getTargetSize').mockReturnValue(4); + base.addSymbol('loop', variable); + + const list = new ScvdList(undefined); + list.name = 'loop'; + list.start = 'start'; + list.limit = 'limit'; + jest.spyOn(list.start!, 'getValue').mockResolvedValue(0); + jest.spyOn(list.limit!, 'getValue').mockResolvedValue(2); + + const stmt = new StatementList(list, undefined); + const child = new CountingStatement(new TestNode(undefined), stmt); + const ctx = createExecutionContext(base); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(child.executed).toBe(2); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-object.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-object.test.ts new file mode 100644 index 00000000..31909504 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-object.test.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementObject. + */ + +import { StatementObject } from '../../../statement-engine/statement-object'; +import { ScvdObject } from '../../../model/scvd-object'; + +describe('StatementObject', () => { + it('constructs with an ScvdObject item', () => { + const item = new ScvdObject(undefined); + const stmt = new StatementObject(item, undefined); + + expect(stmt.scvdItem).toBe(item); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-out.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-out.test.ts new file mode 100644 index 00000000..b75878a7 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-out.test.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementOut. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { StatementOut } from '../../../statement-engine/statement-out'; +import { StatementPrint } from '../../../statement-engine/statement-print'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +describe('StatementOut', () => { + it('skips execution when condition is false', async () => { + const node = new TestNode(undefined, { conditionResult: false, guiName: 'Out' }); + const stmt = new StatementOut(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('sets gui name and executes children', async () => { + const node = new TestNode(undefined, { guiName: 'Out' }); + const stmt = new StatementOut(node, undefined); + const childNode = new TestNode(node, { guiName: 'PrintChild', guiValue: 'Value' }); + new StatementPrint(childNode, stmt); + + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + const outGui = guiTree.children[0]; + expect(outGui.getGuiName()).toBe('Out'); + expect(outGui.children).toHaveLength(1); + expect(outGui.children[0].getGuiName()).toBe('PrintChild'); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-print.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-print.test.ts new file mode 100644 index 00000000..cd3e7604 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-print.test.ts @@ -0,0 +1,68 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementPrint. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { StatementPrint } from '../../../statement-engine/statement-print'; +import { StatementOut } from '../../../statement-engine/statement-out'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +describe('StatementPrint', () => { + it('skips execution when condition is false', async () => { + const node = new TestNode(undefined, { conditionResult: false, guiName: 'Print' }); + const stmt = new StatementPrint(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('creates print gui entries', async () => { + const node = new TestNode(undefined, { guiName: 'Print', guiValue: 'Value' }); + const stmt = new StatementPrint(node, undefined); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + const child = guiTree.children[0]; + expect(child.getGuiName()).toBe('Print'); + expect(child.getGuiValue()).toBe('Value'); + expect(child.isPrint).toBe(true); + }); + + it('executes child statements', async () => { + const node = new TestNode(undefined, { guiName: 'Print', guiValue: 'Value' }); + const stmt = new StatementPrint(node, undefined); + const childNode = new TestNode(node, { guiName: 'Child' }); + new StatementOut(childNode, stmt); + const ctx = createExecutionContext(node); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + const child = guiTree.children[0]; + expect(child.children).toHaveLength(1); + expect(child.children[0].getGuiName()).toBe('Child'); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-read.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-read.test.ts new file mode 100644 index 00000000..dc6b072b --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-read.test.ts @@ -0,0 +1,279 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementRead. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdRead } from '../../../model/scvd-read'; +import type { ScvdDebugTarget } from '../../../scvd-debug-target'; +import { StatementRead } from '../../../statement-engine/statement-read'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +function createRead(_debugTarget: Partial): ScvdRead { + const read = new ScvdRead(undefined); + read.name = 'value'; + read.symbol = 'sym'; + jest.spyOn(read, 'getTargetSize').mockReturnValue(4); + jest.spyOn(read, 'getArraySize').mockResolvedValue(1); + return read; +} + +describe('StatementRead', () => { + it('skips when mustRead is false', async () => { + const read = new ScvdRead(undefined); + read.mustRead = false; + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, {}); + const spy = jest.spyOn(ctx.debugTarget, 'readMemory'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).not.toHaveBeenCalled(); + }); + + it('ignores non-read items', async () => { + const node = new TestNode(undefined); + const stmt = new StatementRead(node, undefined); + const ctx = createExecutionContext(node, {}); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('handles missing name', async () => { + const read = new ScvdRead(undefined); + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles missing target size', async () => { + const read = new ScvdRead(undefined); + read.name = 'value'; + jest.spyOn(read, 'getTargetSize').mockReturnValue(undefined); + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('fails when symbol address is missing', async () => { + const read = createRead({ findSymbolAddress: jest.fn().mockResolvedValue(undefined) }); + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(undefined), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles non-numeric offset values', async () => { + const read = createRead({ findSymbolAddress: jest.fn().mockResolvedValue(0x1000) }); + read.offset = 'offset'; + jest.spyOn(read.offset!, 'getValue').mockResolvedValue('bad'); + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles numeric offsets from symbols', async () => { + const read = createRead({ + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + read.offset = 'offset'; + jest.spyOn(read.offset!, 'getValue').mockResolvedValue(4); + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x1004n, 4); + }); + + it('handles bigint symbol addresses', async () => { + const read = createRead({ + findSymbolAddress: jest.fn().mockResolvedValue(0x2000n), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x2000n), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x2000n, 4); + }); + + it('adds offsets to bigint base addresses', async () => { + const read = createRead({ + findSymbolAddress: jest.fn().mockResolvedValue(0x3000n), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + read.offset = 'offset'; + jest.spyOn(read.offset!, 'getValue').mockResolvedValue(4); + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x3000n), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x3004n, 4); + }); + + it('skips offset updates when BigInt conversion yields undefined', async () => { + const originalBigInt = globalThis.BigInt; + (globalThis as unknown as { BigInt: (value: number) => bigint | undefined }).BigInt = jest.fn(() => undefined); + + const read = createRead({ + findSymbolAddress: jest.fn().mockResolvedValue(0x4000), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + read.offset = 'offset'; + jest.spyOn(read.offset!, 'getValue').mockResolvedValue(4); + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x4000), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + (globalThis as unknown as { BigInt: typeof BigInt }).BigInt = originalBigInt; + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x4000, 4); + }); + + it('handles bigint offsets without symbols', async () => { + const read = new ScvdRead(undefined); + read.name = 'value'; + jest.spyOn(read, 'getTargetSize').mockReturnValue(4); + read.offset = 'offset'; + jest.spyOn(read.offset!, 'getValue').mockResolvedValue(8n); + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(8n, 4); + }); + + it('fails when base address is undefined', async () => { + const read = new ScvdRead(undefined); + read.name = 'value'; + jest.spyOn(read, 'getTargetSize').mockReturnValue(4); + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles read failures from target', async () => { + const read = createRead({ + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + readMemory: jest.fn().mockResolvedValue(undefined), + }); + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + readMemory: jest.fn().mockResolvedValue(undefined), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('reads values and caches const reads', async () => { + const read = createRead({ + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + read.const = 1; + + const stmt = new StatementRead(read, undefined); + const ctx = createExecutionContext(read, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledWith('value', 4, expect.any(Uint8Array), 0, 0x1000, 4); + expect(read.mustRead).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-readlist.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-readlist.test.ts new file mode 100644 index 00000000..12be7f9a --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-readlist.test.ts @@ -0,0 +1,550 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementReadList. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdReadList } from '../../../model/scvd-readlist'; +import type { ScvdDebugTarget } from '../../../scvd-debug-target'; +import { StatementReadList } from '../../../statement-engine/statement-readList'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; +import type { ScvdNode } from '../../../model/scvd-node'; + +function createReadList(): ScvdReadList { + const readList = new ScvdReadList(undefined); + readList.name = 'list'; + readList.symbol = 'sym'; + if (readList.symbol) { + readList.symbol.name = 'sym'; + } + jest.spyOn(readList, 'getTargetSize').mockReturnValue(4); + jest.spyOn(readList, 'getVirtualSize').mockReturnValue(4); + jest.spyOn(readList, 'getIsPointer').mockReturnValue(false); + jest.spyOn(readList, 'getCount').mockResolvedValue(1); + return readList; +} + +function createContext(readList: ScvdReadList, debugTarget: Partial) { + return createExecutionContext(readList, debugTarget); +} + +function createMemberNode(targetSize: number | undefined, memberOffset: number | undefined): ScvdNode { + const node = new TestNode(undefined); + jest.spyOn(node, 'getTargetSize').mockReturnValue(targetSize); + jest.spyOn(node, 'getMemberOffset').mockResolvedValue(memberOffset); + return node; +} + +describe('StatementReadList', () => { + it('skips when mustRead is false', async () => { + const readList = createReadList(); + readList.mustRead = false; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, {}); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(guiTree.children).toHaveLength(0); + }); + + it('logs when cast fails', async () => { + const node = new TestNode(undefined); + const stmt = new StatementReadList(node, undefined); + const ctx = createExecutionContext(node, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires a name', async () => { + const readList = new ScvdReadList(undefined); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles init clear when configured', async () => { + const readList = createReadList(); + readList.init = 1; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'clearVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledWith('list'); + }); + + it('requires target size', async () => { + const readList = createReadList(); + jest.spyOn(readList, 'getTargetSize').mockReturnValue(undefined); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('fails when symbol address is missing', async () => { + const readList = createReadList(); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(undefined), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('rejects non-numeric offsets', async () => { + const readList = createReadList(); + readList.offset = 'offset'; + jest.spyOn(readList.offset!, 'getValue').mockResolvedValue('bad'); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('supports numeric offsets on symbol addresses', async () => { + const readList = createReadList(); + readList.offset = 'offset'; + jest.spyOn(readList.offset!, 'getValue').mockResolvedValue(4); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x1004n, 4); + }); + + it('supports numeric offsets on bigint symbol addresses', async () => { + const readList = createReadList(); + readList.offset = 'offset'; + jest.spyOn(readList.offset!, 'getValue').mockResolvedValue(8); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x2000n), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x2008n, 4); + }); + + it('supports bigint offsets without symbols', async () => { + const readList = new ScvdReadList(undefined); + readList.name = 'list'; + readList.offset = 'offset'; + jest.spyOn(readList.offset!, 'getValue').mockResolvedValue(12n); + jest.spyOn(readList, 'getTargetSize').mockReturnValue(4); + jest.spyOn(readList, 'getVirtualSize').mockReturnValue(4); + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(12n, 4); + }); + + it('fails when base address is undefined', async () => { + const readList = new ScvdReadList(undefined); + readList.name = 'list'; + jest.spyOn(readList, 'getTargetSize').mockReturnValue(4); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, {}); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires type when next is defined', async () => { + const readList = createReadList(); + readList.next = 'next'; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('requires next member size/offset', async () => { + const readList = createReadList(); + readList.next = 'next'; + const member = createMemberNode(undefined, undefined); + (readList as unknown as { _type?: { getMember: () => ScvdNode | undefined; getDisplayLabel: () => string } })._type = { + getMember: () => member, + getDisplayLabel: () => 'Type', + }; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('rejects next member sizes larger than 4', async () => { + const readList = createReadList(); + readList.next = 'next'; + const member = createMemberNode(8, 0); + (readList as unknown as { _type?: { getMember: () => ScvdNode | undefined } })._type = { + getMember: () => member, + }; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles target read failures', async () => { + const readList = createReadList(); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(undefined), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('reads a single item when count and next are undefined', async () => { + const readList = createReadList(); + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('uses pointer sizes when marked as pointer', async () => { + const readList = createReadList(); + jest.spyOn(readList, 'getIsPointer').mockReturnValue(true); + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.debugTarget.readMemory).toHaveBeenCalledWith(0x1000, 4); + }); + + it('uses pointer stride when count is defined', async () => { + const readList = new ScvdReadList(undefined); + readList.name = 'list'; + readList.symbol = 'sym'; + if (readList.symbol) { + readList.symbol.name = 'sym'; + } + jest.spyOn(readList, 'getTargetSize').mockReturnValue(4); + jest.spyOn(readList, 'getVirtualSize').mockReturnValue(4); + jest.spyOn(readList, 'getIsPointer').mockReturnValue(true); + jest.spyOn(readList, 'getCount').mockResolvedValue(2); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(3), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('advances by stride when count is defined and next is missing', async () => { + const readList = new ScvdReadList(undefined); + readList.name = 'list'; + readList.symbol = 'sym'; + if (readList.symbol) { + readList.symbol.name = 'sym'; + } + jest.spyOn(readList, 'getTargetSize').mockReturnValue(4); + jest.spyOn(readList, 'getVirtualSize').mockReturnValue(4); + jest.spyOn(readList, 'getIsPointer').mockReturnValue(false); + jest.spyOn(readList, 'getCount').mockResolvedValue(2); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(3), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('advances by stride with bigint base addresses', async () => { + const readList = new ScvdReadList(undefined); + readList.name = 'list'; + readList.symbol = 'sym'; + if (readList.symbol) { + readList.symbol.name = 'sym'; + } + jest.spyOn(readList, 'getTargetSize').mockReturnValue(4); + jest.spyOn(readList, 'getVirtualSize').mockReturnValue(4); + jest.spyOn(readList, 'getIsPointer').mockReturnValue(false); + jest.spyOn(readList, 'getCount').mockResolvedValue(2); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000n), + getNumArrayElements: jest.fn().mockResolvedValue(3), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('warns when exceeding maximum array size', async () => { + const readList = createReadList(); + jest.spyOn(readList, 'getCount').mockResolvedValue(3); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('breaks when next member is missing', async () => { + const readList = createReadList(); + readList.next = 'next'; + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + (readList as unknown as { _type?: { getMember: () => ScvdNode | undefined } })._type = { + getMember: () => undefined, + }; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('fails when next pointer data is incomplete', async () => { + const readList = createReadList(); + readList.next = 'next'; + jest.spyOn(readList, 'getTargetSize').mockReturnValue(2); + jest.spyOn(readList, 'getVirtualSize').mockReturnValue(2); + jest.spyOn(readList, 'getCount').mockResolvedValue(2); + const member = createMemberNode(4, 0); + (readList as unknown as { _type?: { getMember: () => ScvdNode | undefined } })._type = { + getMember: () => member, + }; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2])), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('handles NULL next pointers', async () => { + const readList = createReadList(); + readList.next = 'next'; + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + const member = createMemberNode(4, 0); + (readList as unknown as { _type?: { getMember: () => ScvdNode | undefined } })._type = { + getMember: () => member, + }; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([0, 0, 0, 0])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(ctx.memoryHost.getVariable('list')).toBeDefined(); + }); + + it('detects linked list loops', async () => { + const readList = createReadList(); + readList.next = 'next'; + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + const member = createMemberNode(4, 0); + (readList as unknown as { _type?: { getMember: () => ScvdNode | undefined } })._type = { + getMember: () => member, + }; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([0x00, 0x10, 0x00, 0x00])), + }); + const guiTree = new ScvdGuiTree(undefined); + const spy = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + it('honors read size maximum', async () => { + const readList = createReadList(); + jest.spyOn(readList, 'getCount').mockResolvedValue(undefined); + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const originalMax = (ScvdReadList as unknown as { READ_SIZE_MAX: number }).READ_SIZE_MAX; + (ScvdReadList as unknown as { READ_SIZE_MAX: number }).READ_SIZE_MAX = 1; + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + (ScvdReadList as unknown as { READ_SIZE_MAX: number }).READ_SIZE_MAX = originalMax; + expect(ctx.memoryHost.getVariable('list')).toBeDefined(); + }); + + it('marks const readlists as initialized', async () => { + const readList = createReadList(); + readList.const = 1; + const stmt = new StatementReadList(readList, undefined); + const ctx = createContext(readList, { + findSymbolAddress: jest.fn().mockResolvedValue(0x1000), + getNumArrayElements: jest.fn().mockResolvedValue(1), + readMemory: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3, 4])), + }); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(readList.mustRead).toBe(false); + }); +}); diff --git a/src/views/component-viewer/test/unit/statement-engine/statement-var.test.ts b/src/views/component-viewer/test/unit/statement-engine/statement-var.test.ts new file mode 100644 index 00000000..fb855b24 --- /dev/null +++ b/src/views/component-viewer/test/unit/statement-engine/statement-var.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2025-2026 Arm Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// generated with AI + +/** + * Unit test for StatementVar. + */ + +import { ScvdGuiTree } from '../../../scvd-gui-tree'; +import { ScvdVar } from '../../../model/scvd-var'; +import { StatementVar } from '../../../statement-engine/statement-var'; +import { createExecutionContext, TestNode } from '../helpers/statement-engine-helpers'; + +describe('StatementVar', () => { + it('writes variables into memory host', async () => { + const item = new ScvdVar(undefined); + item.name = 'varA'; + jest.spyOn(item, 'getTargetSize').mockReturnValue(4); + jest.spyOn(item, 'getValue').mockResolvedValue(123); + + const stmt = new StatementVar(item, undefined); + const ctx = createExecutionContext(item); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).toHaveBeenCalledWith('varA', 4, 123, -1, 0); + }); + + it('skips when required values are missing', async () => { + const item = new ScvdVar(undefined); + jest.spyOn(item, 'getTargetSize').mockReturnValue(4); + jest.spyOn(item, 'getValue').mockResolvedValue(123); + + const stmt = new StatementVar(item, undefined); + const ctx = createExecutionContext(item); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).not.toHaveBeenCalled(); + }); + + it('ignores non-var items', async () => { + const node = new TestNode(undefined); + const stmt = new StatementVar(node, undefined); + const ctx = createExecutionContext(node); + const spy = jest.spyOn(ctx.memoryHost, 'setVariable'); + const guiTree = new ScvdGuiTree(undefined); + + await stmt.executeStatement(ctx, guiTree); + + expect(spy).not.toHaveBeenCalled(); + }); +}); diff --git a/src/views/live-watch/live-watch.ts b/src/views/live-watch/live-watch.ts index 5dc93000..283ac42c 100644 --- a/src/views/live-watch/live-watch.ts +++ b/src/views/live-watch/live-watch.ts @@ -203,7 +203,7 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider=0.6.0" xmlbuilder "~11.0.0" +xml2js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + xmlbuilder2@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder2/-/xmlbuilder2-4.0.1.tgz#424d6b53e2e763d7f90c54684773d9f85c36bb9b"