Files
compiler-explorer/test/parsing-state-tests.ts
Matt Godbolt 04972d6aaa Refactor AsmParser to extract testable components (#7777)
## Summary
- Extracted source line handling logic into a dedicated
`SourceLineHandler` class that consolidates .loc, .stabs, and .6502
directive parsing
- Extracted label processing logic into a `LabelProcessor` class with
methods for finding used labels and filtering
- Created a `ParsingState` class to manage parsing loop state variables
in a centralized way
- Fully integrated all components into the main `AsmParser` class,
replacing the original complex parsing loop

## Changes Made
- **SourceLineHandler**: Unifies `.loc`, `.d2line`, `.cv_loc`, `.dbg`,
`.stabn`, and 6502 debug directive parsing
- **LabelProcessor**: Handles complex label detection, filtering, and
cleanup logic with MIPS/non-MIPS support
- **ParsingState**: Encapsulates state management during parsing
(inNvccCode, inCustomAssembly, etc.)
- **Integration**: All components work together through well-defined
interfaces

## Verification
-  All 1082+ tests pass, including new subclass compatibility tests
from PR #7779
-  All 670+ filter tests pass, confirming exact behavior preservation
-  Added comprehensive unit tests for all new components (32 tests
total)
-  TypeScript compilation and linting pass
-  No performance regression in core functionality

## Bug Fix Discovered
The refactoring inadvertently **fixes issue #7781** - EWAVR label
detection bug:
- **Before**: EWAVR couldn't find labels in usage contexts like `ldi
r16, HIGH(_data)` due to `labelFindFor()` returning definition regex
- **After**: Now correctly uses `identifierFindRe` to find labels in
usage contexts
- Updated tests to reflect the corrected behavior

## Benefits
- Reduced complexity in the main `processAsm` method (from 180+ lines to
more manageable chunks)
- Extracted highly testable, focused components with single
responsibilities
- Eliminated code duplication between source handling methods
- Centralized state management reduces scattered variable handling
- Maintained full backward compatibility and exact behavior
- Fixed EWAVR label detection bug as a side effect

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-11 18:22:44 -05:00

213 lines
8.1 KiB
TypeScript

// 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 {ParsingState} from '../lib/parsers/parsing-state.js';
describe('ParsingState tests', () => {
const files = {1: '/path/to/file.cpp'};
let state: ParsingState;
beforeEach(() => {
state = new ParsingState(files, null, '', false, false, []);
});
describe('construction and initialization', () => {
it('should initialize with correct default values', () => {
expect(state.files).toBe(files);
expect(state.source).toBeNull();
expect(state.prevLabel).toBe('');
expect(state.prevLabelIsUserFunction).toBe(false);
expect(state.dontMaskFilenames).toBe(false);
expect(state.mayRemovePreviousLabel).toBe(true);
expect(state.keepInlineCode).toBe(false);
expect(state.inNvccDef).toBe(false);
expect(state.inNvccCode).toBe(false);
expect(state.inCustomAssembly).toBe(0);
expect(state.inVLIWpacket).toBe(false);
expect(state.getCurrentLineIndex()).toBe(0);
});
});
describe('source management', () => {
it('should update source and track own source', () => {
const source = {file: null, line: 42, mainsource: true};
state.updateSource(source);
expect(state.source).toBe(source);
expect(state.lastOwnSource).toBe(source);
});
it('should not update lastOwnSource for library sources', () => {
const librarySource = {file: '/usr/include/stdio.h', line: 100};
state.updateSource(librarySource);
expect(state.source).toBe(librarySource);
expect(state.lastOwnSource).toBeNull();
});
it('should reset state on block end', () => {
state.updateSource({file: null, line: 42});
state.updatePrevLabel('test_label');
state.resetToBlockEnd();
expect(state.source).toBeNull();
expect(state.prevLabel).toBe('');
expect(state.lastOwnSource).toBeNull();
});
});
describe('custom assembly handling', () => {
it('should track custom assembly nesting', () => {
expect(state.isInCustomAssembly()).toBe(false);
state.enterCustomAssembly();
expect(state.isInCustomAssembly()).toBe(true);
expect(state.inCustomAssembly).toBe(1);
state.enterCustomAssembly();
expect(state.inCustomAssembly).toBe(2);
state.exitCustomAssembly();
expect(state.inCustomAssembly).toBe(1);
expect(state.isInCustomAssembly()).toBe(true);
state.exitCustomAssembly();
expect(state.isInCustomAssembly()).toBe(false);
});
});
describe('NVCC state management', () => {
it('should handle NVCC definition state', () => {
expect(state.inNvccDef).toBe(false);
expect(state.inNvccCode).toBe(false);
state.enterNvccDef();
expect(state.inNvccDef).toBe(true);
expect(state.inNvccCode).toBe(true);
state.exitNvccDef();
expect(state.inNvccDef).toBe(false);
expect(state.inNvccCode).toBe(true);
});
});
describe('library code filtering', () => {
it('should filter library code when conditions are met', () => {
state.updatePrevLabel('lib_func', false); // not user function
state.updateSource({file: '/usr/lib/library.so', line: 100}); // library source
const result = state.shouldFilterLibraryCode({libraryCode: true});
expect(result).toBe(true);
});
it('should not filter when user function', () => {
state.updatePrevLabel('user_func', true); // user function
state.updateSource({file: '/usr/lib/library.so', line: 100});
const result = state.shouldFilterLibraryCode({libraryCode: true});
expect(result).toBe(false);
});
it('should not filter when has own source', () => {
state.updatePrevLabel('lib_func', false);
state.updateSource({file: null, line: 42, mainsource: true}); // own source
const result = state.shouldFilterLibraryCode({libraryCode: true});
expect(result).toBe(false);
});
it('should not filter when source is undefined', () => {
state.updatePrevLabel('lib_func', false); // not user function
state.updateSource(undefined); // no source
const result = state.shouldFilterLibraryCode({libraryCode: true});
expect(result).toBe(false);
});
it('should not filter when source is null', () => {
state.updatePrevLabel('lib_func', false); // not user function
state.updateSource(null); // no source
const result = state.shouldFilterLibraryCode({libraryCode: true});
expect(result).toBe(false);
});
});
describe('label management', () => {
it('should update and clear prev label', () => {
state.updatePrevLabel('test_label', true);
expect(state.prevLabel).toBe('test_label');
expect(state.prevLabelIsUserFunction).toBe(true);
state.clearPrevLabel();
expect(state.prevLabel).toBe('');
expect(state.prevLabelIsUserFunction).toBe(false);
});
});
describe('line iteration', () => {
it('should iterate through lines', () => {
const testLines = ['line1', 'line2', 'line3'];
const iterableState = new ParsingState({}, null, '', false, false, testLines);
const lines: string[] = [];
for (const line of iterableState) {
lines.push(line);
}
expect(lines).toEqual(testLines);
});
it('should track current index during iteration', () => {
const testLines = ['line1', 'line2'];
const iterableState = new ParsingState({}, null, '', false, false, testLines);
expect(iterableState.getCurrentLineIndex()).toBe(0);
const iterator = iterableState[Symbol.iterator]();
iterator.next();
expect(iterableState.getCurrentLineIndex()).toBe(1);
iterator.next();
expect(iterableState.getCurrentLineIndex()).toBe(2);
});
});
describe('flags management', () => {
it('should manage removal and inline code flags', () => {
expect(state.shouldRemovePreviousLabel()).toBe(true);
expect(state.shouldKeepInlineCode()).toBe(false);
state.setMayRemovePreviousLabel(false);
state.setKeepInlineCode(true);
expect(state.shouldRemovePreviousLabel()).toBe(false);
expect(state.shouldKeepInlineCode()).toBe(true);
});
});
});