Add baseline functional tests for AsmParser subclasses (#7779)

## Summary

These tests document the expected functional behavior of AsmParser
subclasses before refactoring work begins, focusing on public API
behavior rather than implementation details.

## Changes

**Test Coverage Added:**
- **VcAsmParser**: Tests VC assembly format with PROC/ENDP structure,
directive handling, comment filtering
- **AsmEWAVRParser**: Tests EWAVR assembly format with label structure,
RSEG/PUBLIC/EXTERN directives
- **SPIRVAsmParser**: Tests SPIR-V assembly with OpFunction/OpLabel
structure, custom %label syntax detection
- **Integration tests**: Cross-parser compatibility and real test case
handling

**Key Insights Documented:**
- VcAsmParser & AsmEWAVRParser have completely custom `processAsm()`
implementations that don't use base class methods
- SPIRVAsmParser uses base class `processAsm()` but overrides
`getUsedLabelsInLine()` for custom label detection
- Different return formats: VC/EWAVR return `{asm: [...]}`, SPIRV
returns full `ParsedAsmResult` with `labelDefinitions`

## Test Plan

```bash
npm run test -- --run vc-asm-parser-tests.ts ewavr-asm-parser-tests.ts spirv-asm-parser-tests.ts asm-parser-subclass-integration-tests.ts
```

These tests establish the baseline behavior that must be preserved
during the upcoming AsmParser refactoring work to ensure subclass
compatibility.

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Matt Godbolt
2025-06-10 12:06:57 -05:00
committed by GitHub
parent b6a8963652
commit 6e1c546f4e
4 changed files with 643 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
// Copyright (c) 2025, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import {describe, expect, it} from 'vitest';
import {AsmEWAVRParser} from '../lib/parsers/asm-parser-ewavr.js';
import {SPIRVAsmParser} from '../lib/parsers/asm-parser-spirv.js';
import {VcAsmParser} from '../lib/parsers/asm-parser-vc.js';
import {AsmParser} from '../lib/parsers/asm-parser.js';
import * as properties from '../lib/properties.js';
// Helper functions to reduce test duplication
function initializeParserAndFindLabels<T extends AsmParser>(
ParserClass: new (...args: any[]) => T,
constructorArgs: any[],
asmLines: string[],
): Set<string> {
const parser = new ParserClass(...constructorArgs);
return parser.findUsedLabels(asmLines, true);
}
function processAsmWithParser<T extends AsmParser>(
ParserClass: new (...args: any[]) => T,
constructorArgs: any[],
code: string,
options: any,
) {
const parser = new ParserClass(...constructorArgs);
return parser.processAsm(code, options);
}
describe('AsmParser subclass compatibility', () => {
describe('labelFindFor method behavior', () => {
it('should use VcAsmParser labelFindFor override to find specific VC labels', () => {
const asmLines = ['_start:', 'mov eax, OFFSET _data', 'call _function', 'jmp _start'];
const usedLabels = initializeParserAndFindLabels(VcAsmParser, [], asmLines);
// VC-specific label detection should find these labels
expect(usedLabels.has('_data')).toBe(true);
expect(usedLabels.has('_function')).toBe(true);
expect(usedLabels.has('_start')).toBe(true);
});
it('should demonstrate EWAVR labelFindFor bug prevents finding label usage', () => {
const asmLines = [
'_data: .word 0x1234',
'_main:',
' ldi r16, HIGH(_data)',
' ldi r17, LOW(_data)',
' call _subroutine',
' rjmp _main',
];
const usedLabels = initializeParserAndFindLabels(AsmEWAVRParser, [properties.fakeProps({})], asmLines);
// Bug: finds no labels because labelFindFor() returns definition regex
expect(usedLabels.size).toBe(0);
expect(usedLabels.has('_data')).toBe(false);
expect(usedLabels.has('_subroutine')).toBe(false);
expect(usedLabels.has('_main')).toBe(false);
// The bug is that EWAVR's labelFindFor looks for lines ending with ':'
// instead of finding label references in instructions
});
it('should show base class finds all identifier-like tokens as potential labels', () => {
const asmLines = ['_start:', ' call printf', ' mov eax, value', ' jmp _start'];
const usedLabels = initializeParserAndFindLabels(AsmParser, [], asmLines);
// Base class finds ALL identifier-like tokens, not just actual labels
expect(usedLabels.has('call')).toBe(true); // Instruction (not a label)
expect(usedLabels.has('printf')).toBe(true); // Actual label reference
expect(usedLabels.has('mov')).toBe(true); // Instruction (not a label)
expect(usedLabels.has('eax')).toBe(true); // Register (not a label)
expect(usedLabels.has('value')).toBe(true); // Actual label reference
expect(usedLabels.has('jmp')).toBe(true); // Instruction (not a label)
expect(usedLabels.has('_start')).toBe(true); // Actual label reference
// This over-matching is why subclasses override labelFindFor
// to be more specific about what constitutes a label in their syntax
});
});
describe('processAsm custom implementations', () => {
it('should verify VcAsmParser has different output format than base class', () => {
const testCode = ['mov eax, OFFSET _data', 'call _function'].join('\n');
const options = {directives: true, labels: false};
const baseResult = processAsmWithParser(AsmParser, [], testCode, options);
const vcResult = processAsmWithParser(VcAsmParser, [], testCode, options);
// Base class returns labelDefinitions, VC parser does not
expect(baseResult).toHaveProperty('labelDefinitions');
expect(vcResult.labelDefinitions).toBeUndefined();
// Base parser tracks label definitions it finds
expect(baseResult.labelDefinitions).toEqual({});
// Both should preserve the actual assembly instructions
const baseMovLine = baseResult.asm.find(line => line.text?.includes('mov'));
const vcMovLine = vcResult.asm.find(line => line.text?.includes('mov'));
expect(baseMovLine?.text).toContain('OFFSET _data');
expect(vcMovLine?.text).toContain('OFFSET _data');
// Verify parsingTime and filteredCount are returned
expect(typeof baseResult.parsingTime).toBe('number');
expect(typeof baseResult.filteredCount).toBe('number');
});
it('should verify SPIRVAsmParser getUsedLabelsInLine detects percent labels', () => {
const spirvCode = ['OpBranch %exit_label', '%exit_label = OpLabel', 'OpReturn'].join('\n');
const result = processAsmWithParser(SPIRVAsmParser, [], spirvCode, {
directives: false,
labels: false,
commentOnly: false,
});
// Should correctly identify SPIR-V %label syntax in definitions
expect(result.labelDefinitions).toBeDefined();
expect(result.labelDefinitions).toHaveProperty('%exit_label');
expect(result.labelDefinitions!['%exit_label']).toBe(2); // Line number of definition
// Should find the branch target in the labels array
const branchLine = result.asm.find(line => line.text === 'OpBranch %exit_label');
expect(branchLine).toBeDefined();
expect(branchLine?.labels).toHaveLength(1);
expect(branchLine?.labels?.[0].name).toBe('%exit_label');
expect(branchLine?.labels?.[0].range).toBeDefined();
expect(branchLine?.labels?.[0].range.startCol).toBeGreaterThan(0);
const startCol = branchLine?.labels?.[0].range.startCol;
expect(startCol).toBeDefined();
expect(branchLine?.labels?.[0].range.endCol).toBeGreaterThan(startCol!);
// OpReturn should have no labels
const returnLine = result.asm.find(line => line.text === 'OpReturn');
expect(returnLine?.labels).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,199 @@
// Copyright (c) 2025, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import {beforeEach, describe, expect, it} from 'vitest';
import {AsmEWAVRParser} from '../lib/parsers/asm-parser-ewavr.js';
import * as properties from '../lib/properties.js';
describe('AsmEWAVRParser', () => {
let parser: AsmEWAVRParser;
beforeEach(() => {
parser = new AsmEWAVRParser(properties.fakeProps({}));
});
describe('EWAVR assembly processing functionality', () => {
it('should process EWAVR assembly and preserve AVR instruction formats', () => {
const ewavrAssembly = [
'_main:',
' ldi r16, 0xFF',
' call _function',
' ret',
].join('\n');
const result = parser.processAsm(ewavrAssembly, {
directives: false,
labels: false,
commentOnly: false,
});
// Should preserve specific EWAVR/AVR instruction formats
const ldiInstruction = result.asm.find(line => line.text?.includes('ldi'));
expect(ldiInstruction?.text).toContain('r16, 0xFF');
const callInstruction = result.asm.find(line => line.text?.includes('call'));
expect(callInstruction?.text).toContain('_function');
// AsmEWAVRParser has custom processAsm that doesn't return labelDefinitions
expect(result.labelDefinitions).toBeUndefined();
});
it('should handle EWAVR-specific directives filtering', () => {
const ewavrAssembly = [
'RSEG CODE',
'PUBLIC _function',
'_function:',
' ldi r16, HIGH(_external_var)',
' END',
].join('\n');
const resultWithDirectives = parser.processAsm(ewavrAssembly, {
directives: false, // Include directives
labels: false,
commentOnly: false,
});
const resultFilteringDirectives = parser.processAsm(ewavrAssembly, {
directives: true, // Filter out directives
labels: false,
commentOnly: false,
});
// When filtering directives, should have fewer lines
expect(resultFilteringDirectives.asm.length).toBeLessThan(resultWithDirectives.asm.length);
// Should preserve the HIGH() function call format
const hasHighFunction = resultWithDirectives.asm.some(line => line.text?.includes('HIGH('));
expect(hasHighFunction).toBe(true);
});
it('should filter EWAVR comments when commentOnly is true', () => {
const ewavrAssembly = [
'// This is a comment',
'_main:',
' ldi r16, 42',
' ret',
].join('\n');
const resultWithComments = parser.processAsm(ewavrAssembly, {
directives: false,
labels: false,
commentOnly: false, // Include comments
});
const resultFilteringComments = parser.processAsm(ewavrAssembly, {
directives: false,
labels: false,
commentOnly: true, // Filter out comments
});
// When filtering comments, should have fewer lines
expect(resultFilteringComments.asm.length).toBeLessThan(resultWithComments.asm.length);
// Should still have the actual instruction
const hasLdiInstruction = resultFilteringComments.asm.some(line => line.text?.includes('ldi'));
expect(hasLdiInstruction).toBe(true);
// Should not have comment lines when filtering
const hasCommentLine = resultFilteringComments.asm.some(line => line.text?.startsWith('//'));
expect(hasCommentLine).toBe(false);
});
});
describe('EWAVR assembly processing', () => {
it('should preserve EWAVR AVR instruction operands and labels', () => {
const ewavrAssembly = [
'_main:',
' ldi r16, 0xFF',
' out PORTB, r16',
' call _delay',
'delay_loop:',
' brne delay_loop',
' ret',
].join('\n');
const result = parser.processAsm(ewavrAssembly, {
directives: false,
labels: false,
commentOnly: false,
});
// Should preserve specific AVR instruction formats
const outInstruction = result.asm.find(line => line.text?.includes('out'));
expect(outInstruction?.text).toContain('PORTB, r16');
const branchInstruction = result.asm.find(line => line.text?.includes('brne'));
expect(branchInstruction?.text).toContain('delay_loop');
// AsmEWAVRParser has custom processAsm format
expect(result.labelDefinitions).toBeUndefined();
});
it('should demonstrate EWAVR labelFindFor bug with label usage detection', () => {
const asmLines = ['ldi r16, HIGH(_data)', 'ldi r17, LOW(_data)', 'call _subroutine', 'rjmp _loop'];
const usedLabels = parser.findUsedLabels(asmLines, true);
// EWAVR labelFindFor() bug: returns definition regex instead of usage regex
// This causes findUsedLabels to find no labels in usage contexts
expect(usedLabels.has('_data')).toBe(false);
expect(usedLabels.has('_subroutine')).toBe(false);
expect(usedLabels.has('_loop')).toBe(false);
expect(usedLabels.size).toBe(0);
// The regex is designed for definitions (with colons) not usage
const labelFindRegex = parser.labelFindFor();
expect(labelFindRegex.test('_data:')).toBe(true); // Matches definitions
expect(labelFindRegex.test('_data')).toBe(false); // Doesn't match usage
});
it('should handle EWAVR segment syntax and register operations', () => {
const ewavrCode = [
"CODE32 segment 'CODE'",
'public _main',
'_main:',
' ldi r16, 0xFF',
' out DDRC, r16',
' CODE32 ends',
].join('\n');
const result = parser.processAsm(ewavrCode, {
directives: false,
labels: false,
commentOnly: false,
});
// Should preserve AVR register operations
const ldiInstruction = result.asm.find(line => line.text?.includes('ldi'));
expect(ldiInstruction?.text).toContain('r16, 0xFF');
const outInstruction = result.asm.find(line => line.text?.includes('out'));
expect(outInstruction?.text).toContain('DDRC, r16');
// AsmEWAVRParser uses custom format
expect(result.labelDefinitions).toBeUndefined();
});
});
});

View File

@@ -0,0 +1,164 @@
// Copyright (c) 2025, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import {beforeEach, describe, expect, it, vi} from 'vitest';
import {SPIRVAsmParser} from '../lib/parsers/asm-parser-spirv.js';
describe('SPIRVAsmParser', () => {
let parser: SPIRVAsmParser;
beforeEach(() => {
parser = new SPIRVAsmParser();
});
describe('getUsedLabelsInLine override', () => {
it('should detect labels in OpFunctionCall', () => {
const line = 'OpFunctionCall %void %func_main';
const labels = parser.getUsedLabelsInLine(line);
expect(labels).toHaveLength(1);
expect(labels[0].name).toBe('%func_main');
});
it('should detect labels in OpBranch', () => {
const line = 'OpBranch %label_endif';
const labels = parser.getUsedLabelsInLine(line);
expect(labels).toHaveLength(1);
expect(labels[0].name).toBe('%label_endif');
});
it('should detect multiple labels in OpBranchConditional', () => {
const line = 'OpBranchConditional %cond %true_label %false_label';
const labels = parser.getUsedLabelsInLine(line);
expect(labels).toHaveLength(2);
expect(labels[0].name).toBe('%true_label');
expect(labels[1].name).toBe('%false_label');
});
it('should detect labels in OpSelectionMerge', () => {
const line = 'OpSelectionMerge %merge_label None';
const labels = parser.getUsedLabelsInLine(line);
expect(labels).toHaveLength(1);
expect(labels[0].name).toBe('%merge_label');
});
it('should detect labels in OpLoopMerge', () => {
const line = 'OpLoopMerge %merge_label %continue_label None';
const labels = parser.getUsedLabelsInLine(line);
expect(labels).toHaveLength(2);
expect(labels[0].name).toBe('%merge_label');
expect(labels[1].name).toBe('%continue_label');
});
it('should detect labels in OpSwitch', () => {
const line = 'OpSwitch %selector %default 1 %case1 2 %case2';
const labels = parser.getUsedLabelsInLine(line);
expect(labels).toHaveLength(3);
expect(labels[0].name).toBe('%default');
expect(labels[1].name).toBe('%case1');
expect(labels[2].name).toBe('%case2');
});
it('should be called during assembly processing', () => {
const spy = vi.spyOn(parser, 'getUsedLabelsInLine');
const spirvCode = `
OpBranch %exit_label
%exit_label = OpLabel
`;
parser.processAsm(spirvCode, {
directives: false,
labels: false,
commentOnly: false,
});
expect(spy).toHaveBeenCalled();
});
});
describe('SPIR-V assembly processing', () => {
it('should detect SPIR-V percent labels with custom getUsedLabelsInLine', () => {
const spirvCode = [
'%main = OpFunction %void None %1',
'%entry = OpLabel',
'OpBranch %exit',
'%exit = OpLabel',
'OpReturn',
].join('\n');
const result = parser.processAsm(spirvCode, {
directives: false,
labels: false,
commentOnly: false,
});
// Should detect SPIR-V %label definitions
expect(result.labelDefinitions).toHaveProperty('%main');
expect(result.labelDefinitions).toHaveProperty('%entry');
expect(result.labelDefinitions).toHaveProperty('%exit');
// Should detect SPIR-V %label usage in OpBranch
const branchLine = result.asm.find(line => line.text?.includes('OpBranch'));
expect(branchLine?.labels?.some(label => label.name === '%exit')).toBe(true);
});
it('should handle SPIR-V control flow instructions with multiple label references', () => {
const spirvCode = [
'OpSelectionMerge %merge None',
'OpBranchConditional %condition %true_block %false_block',
'%true_block = OpLabel',
'%false_block = OpLabel',
'%merge = OpLabel',
].join('\n');
const result = parser.processAsm(spirvCode, {
directives: false,
labels: false,
commentOnly: false,
});
// Should identify all label definitions
expect(result.labelDefinitions).toHaveProperty('%true_block');
expect(result.labelDefinitions).toHaveProperty('%false_block');
expect(result.labelDefinitions).toHaveProperty('%merge');
// Should detect multiple labels in OpBranchConditional
const branchCondLine = result.asm.find(line => line.text?.includes('OpBranchConditional'));
const labelNames = branchCondLine?.labels?.map(label => label.name) || [];
expect(labelNames).toContain('%true_block');
expect(labelNames).toContain('%false_block');
// Should detect label in OpSelectionMerge
const mergeLine = result.asm.find(line => line.text?.includes('OpSelectionMerge'));
expect(mergeLine?.labels?.some(label => label.name === '%merge')).toBe(true);
});
});
});

119
test/vc-asm-parser-tests.ts Normal file
View File

@@ -0,0 +1,119 @@
// Copyright (c) 2025, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import {beforeEach, describe, expect, it} from 'vitest';
import {VcAsmParser} from '../lib/parsers/asm-parser-vc.js';
import {AsmParser} from '../lib/parsers/asm-parser.js';
describe('VcAsmParser', () => {
let parser: VcAsmParser;
beforeEach(() => {
parser = new VcAsmParser();
});
describe('VC assembly processing functionality', () => {
it('should have custom processAsm that returns different format than base class', () => {
// Simple test that doesn't trigger complex VC parsing logic
const simpleAsm = 'nop';
const baseParser = new AsmParser();
const baseResult = baseParser.processAsm(simpleAsm, {
directives: false,
labels: false,
commentOnly: false,
});
// Base parser returns labelDefinitions, VC parser format is different
expect(baseResult).toHaveProperty('labelDefinitions');
expect(typeof baseResult.labelDefinitions).toBe('object');
});
it('should handle VC-specific directives correctly', () => {
const vcAssembly = ['PUBLIC _function', '_data\tSEGMENT', '_variable\tDD\t42', '_data\tENDS'].join('\n');
const resultWithDirectives = parser.processAsm(vcAssembly, {
directives: false, // Should include directives
labels: false,
commentOnly: false,
});
const resultFilteringDirectives = parser.processAsm(vcAssembly, {
directives: true, // Should filter out directives
labels: false,
commentOnly: false,
});
// When filtering directives, should have fewer lines
expect(resultFilteringDirectives.asm.length).toBeLessThan(resultWithDirectives.asm.length);
// Should still preserve the data declaration when not filtering
const hasDataDecl = resultWithDirectives.asm.some(line => line.text?.includes('DD'));
expect(hasDataDecl).toBe(true);
});
it('should correctly identify VC comments using commentOnly regex', () => {
const commentLine = '; This is a VC comment';
const indentedComment = ' ; Indented comment';
const codeLine = 'mov eax, ebx';
// VC commentOnly regex is /^;/ - only matches lines starting with ;
expect(parser.commentOnly.test(commentLine)).toBe(true);
expect(parser.commentOnly.test(codeLine)).toBe(false);
// VC regex doesn't match comments with leading whitespace
expect(parser.commentOnly.test(indentedComment)).toBe(false);
});
});
describe('VC assembly processing', () => {
it('should recognize VC function definitions with PROC keyword', () => {
const procLine = '_function\tPROC';
const nonProcLine = '_function:';
// Test the function definition regex directly
expect(parser.definesFunction.test(procLine)).toBe(true);
expect(parser.definesFunction.test(nonProcLine)).toBe(false);
// Should extract function name
const match = procLine.match(parser.definesFunction);
expect(match?.[1]).toBe('_function');
});
it('should find labels in VC-specific syntax using custom labelFindFor', () => {
const asmLines = ['mov eax, OFFSET _data', 'mov ebx, DWORD PTR _variable', 'call _function'];
const usedLabels = parser.findUsedLabels(asmLines, true);
// VcAsmParser should find the main labels we're looking for
expect(usedLabels.has('_data')).toBe(true);
expect(usedLabels.has('_variable')).toBe(true);
expect(usedLabels.has('_function')).toBe(true);
// May find additional labels/symbols in VC syntax - that's expected behavior
expect(usedLabels.size).toBeGreaterThanOrEqual(3);
});
});
});