Sourcemap
Description
Section titled “Description”This example demonstrates how to use ProgramSourceMap for mapping TEAL program counters (PC) to source locations for debugging purposes. Topics covered:
- ProgramSourceMap class construction from sourcemap data
- Sourcemap format: version, sources, names, mappings
- getPcs() to get all program counter values
- getLocationForPc() to get source location for a specific PC
- SourceLocation properties: line, column, sourceIndex, nameIndex
- getPcsOnSourceLine() to find PCs for a source line
- How sourcemaps enable TEAL debugging by mapping PC to source
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example common/12-sourcemap.ts/** * Example: Sourcemap * * This example demonstrates how to use ProgramSourceMap for mapping TEAL * program counters (PC) to source locations for debugging purposes. * * Topics covered: * - ProgramSourceMap class construction from sourcemap data * - Sourcemap format: version, sources, names, mappings * - getPcs() to get all program counter values * - getLocationForPc() to get source location for a specific PC * - SourceLocation properties: line, column, sourceIndex, nameIndex * - getPcsOnSourceLine() to find PCs for a source line * - How sourcemaps enable TEAL debugging by mapping PC to source * * Prerequisites: * - No LocalNet required */
import type { PcLineLocation } from '@algorandfoundation/algokit-utils/common';import { ProgramSourceMap } from '@algorandfoundation/algokit-utils/common';import { printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js';
// ============================================================================// Main Example// ============================================================================
printHeader('Sourcemap Example');
// ============================================================================// Step 1: Introduction to Sourcemaps// ============================================================================printStep(1, 'Introduction to Sourcemaps');
printInfo('Sourcemaps enable debugging by mapping compiled code to source code.');printInfo('');printInfo('In Algorand development:');printInfo(' - TEAL programs are compiled from higher-level languages (PyTeal, Reach, etc.)');printInfo(' - Program counter (PC) values identify positions in the compiled TEAL');printInfo(' - Sourcemaps map each PC back to the original source file location');printInfo(' - When errors occur, you can trace back to your original source code');printInfo('');printInfo('The ProgramSourceMap class parses standard source map v3 format.');printSuccess('Sourcemaps bridge the gap between compiled TEAL and source code');
// ============================================================================// Step 2: Sourcemap Format// ============================================================================printStep(2, 'Sourcemap Format');
printInfo('A sourcemap follows the standard v3 format with these fields:');printInfo('');printInfo(' version: number - Sourcemap version (must be 3)');printInfo(' sources: string[] - List of source file names/paths');printInfo(' names: string[] - List of symbol names referenced in mappings');printInfo(' mappings: string - VLQ-encoded mapping data (semicolon-separated)');printInfo('');printInfo('The mappings string uses Base64 VLQ encoding:');printInfo(' - Each semicolon (;) represents one TEAL instruction (one PC value)');printInfo(' - Within each segment, values are delta-encoded from the previous');printInfo( ' - Segments encode: [generated_col, source_index, source_line, source_col, name_index?]',);printInfo('');printSuccess('Standard v3 sourcemap format enables interoperability with tools');
// ============================================================================// Step 3: Creating a Mock Sourcemap// ============================================================================printStep(3, 'Creating a Mock Sourcemap');
printInfo('Let us create a mock sourcemap representing a simple TEAL program.');printInfo('');printInfo('Imagine this PyTeal source (pysource.py):');printInfo(' Line 1: from pyteal import *');printInfo(' Line 2: ');printInfo(' Line 3: def approval():');printInfo(' Line 4: return Approve()');printInfo(' Line 5: ');printInfo(' Line 6: print(compileTeal(approval()))');printInfo('');printInfo('Compiled to TEAL:');printInfo(' PC 0: #pragma version 10');printInfo(' PC 1: int 1');printInfo(' PC 2: return');printInfo('');
// Create a mock sourcemap that maps:// PC 0 -> Line 1 (version pragma from import)// PC 1 -> Line 4, column 11 (the Approve() call, with name index 0)// PC 2 -> Line 4, column 4 (the return statement, with name index 1)// Note: VLQ encoding - each semicolon separates PC values// Format: [gen_col, source_idx, source_line, source_col, name_idx?]// Values are delta-encoded from previous
const mockSourcemap = { version: 3, sources: ['pysource.py'], names: ['Approve', 'return'], // Mappings explanation (VLQ-encoded, delta from previous): // PC 0: AAAA = [0, 0, 0, 0] -> source 0, line 0, col 0 (no name) // PC 1: AAGKA = [0, 0, 3, 5, 0] -> source 0, line 3, col 5, name 0 ("Approve") // PC 2: AAAAC = [0, 0, 0, 0, 1] -> same source/line/col, name delta +1 -> name 1 ("return") mappings: 'AAAA;AAGKA;AAAAC',};
printInfo(`version: ${mockSourcemap.version}`);printInfo(`sources: ${JSON.stringify(mockSourcemap.sources)}`);printInfo(`names: ${JSON.stringify(mockSourcemap.names)}`);printInfo(`mappings: "${mockSourcemap.mappings}"`);printInfo('');printSuccess('Mock sourcemap created representing a simple PyTeal program');
// ============================================================================// Step 4: Constructing ProgramSourceMap// ============================================================================printStep(4, 'Constructing ProgramSourceMap');
const sourceMap = new ProgramSourceMap(mockSourcemap);
printInfo('ProgramSourceMap constructor takes an object with:');printInfo(' { version, sources, names, mappings }');printInfo('');printInfo('After construction, the sourcemap properties are accessible:');printInfo(` sourceMap.version: ${sourceMap.version}`);printInfo(` sourceMap.sources: ${JSON.stringify(sourceMap.sources)}`);printInfo(` sourceMap.names: ${JSON.stringify(sourceMap.names)}`);printInfo(` sourceMap.mappings: "${sourceMap.mappings}"`);printInfo('');printInfo('The constructor parses the VLQ-encoded mappings and builds internal indexes.');printSuccess('ProgramSourceMap constructed and mappings parsed');
// ============================================================================// Step 5: getPcs() - Get All Program Counter Values// ============================================================================printStep(5, 'getPcs() - Get All Program Counter Values');
const allPcs = sourceMap.getPcs();
printInfo('getPcs() returns an array of all PC values that have mappings.');printInfo('');printInfo(`All PCs with mappings: [${allPcs.join(', ')}]`);printInfo(`Total PC count: ${allPcs.length}`);printInfo('');printInfo('Not all PCs may have mappings - some TEAL opcodes may not');printInfo('correspond to any source location (e.g., compiler-generated code).');printSuccess('getPcs() retrieves all program counters with source mappings');
// ============================================================================// Step 6: getLocationForPc() - Get Source Location for a PC// ============================================================================printStep(6, 'getLocationForPc() - Get Source Location for a PC');
printInfo('getLocationForPc(pc) returns the source location for a given PC.');printInfo('');
printInfo('SourceLocation Interface');printInfo(' interface SourceLocation {');printInfo(' line: number // Line number in source file (0-indexed)');printInfo(' column: number // Column number in source line (0-indexed)');printInfo(' sourceIndex: number // Index into sources array');printInfo(' nameIndex?: number // Optional index into names array');printInfo(' }');printInfo('');
// Get location for each PCfor (const pc of allPcs) { const location = sourceMap.getLocationForPc(pc); if (location) { const sourceName = sourceMap.sources[location.sourceIndex]; const nameInfo = location.nameIndex !== undefined ? `, name: "${sourceMap.names[location.nameIndex]}"` : ''; printInfo(`PC ${pc} -> ${sourceName}:${location.line + 1}:${location.column + 1}${nameInfo}`); printInfo( ` (line: ${location.line}, column: ${location.column}, sourceIndex: ${location.sourceIndex}${location.nameIndex !== undefined ? `, nameIndex: ${location.nameIndex}` : ''})`, ); }}printInfo('');
// Try a PC that doesn't existconst nonExistentPc = 999;const noLocation = sourceMap.getLocationForPc(nonExistentPc);printInfo(`PC ${nonExistentPc} (non-existent) -> ${noLocation ?? 'undefined'}`);printInfo('');printSuccess('getLocationForPc() maps PC values to source locations');
// ============================================================================// Step 7: getPcsOnSourceLine() - Find PCs for a Source Line// ============================================================================printStep(7, 'getPcsOnSourceLine() - Find PCs for a Source Line');
printInfo('getPcsOnSourceLine(sourceIndex, line) returns all PCs for a source line.');printInfo('This is useful for setting breakpoints at a specific source line.');printInfo('');
printInfo('PcLineLocation Interface');printInfo(' interface PcLineLocation {');printInfo(' pc: number // Program counter value');printInfo(' column: number // Column in the source line');printInfo(' nameIndex?: number // Optional index into names array');printInfo(' }');printInfo('');
// Check each line in our sourceconst sourceIndex = 0;const linesToCheck = [0, 1, 2, 3, 4, 5];
for (const line of linesToCheck) { const pcsOnLine = sourceMap.getPcsOnSourceLine(sourceIndex, line); if (pcsOnLine.length > 0) { const pcDetails = pcsOnLine.map((loc: PcLineLocation) => { const nameInfo = loc.nameIndex !== undefined ? ` (${sourceMap.names[loc.nameIndex]})` : ''; return `PC ${loc.pc} at col ${loc.column}${nameInfo}`; }); printInfo(`Line ${line + 1}: ${pcDetails.join(', ')}`); } else { printInfo(`Line ${line + 1}: (no PCs mapped)`); }}printInfo('');printSuccess('getPcsOnSourceLine() enables breakpoint setting and line-based debugging');
// ============================================================================// Step 8: Practical Debugging Example// ============================================================================printStep(8, 'Practical Debugging Example');
printInfo('When a TEAL program fails, the error includes the PC where it failed.');printInfo('Sourcemaps let you trace back to your original source code.');printInfo('');
// Simulate a runtime error scenarioconst failedPc = 1;const errorLocation = sourceMap.getLocationForPc(failedPc);
printInfo('Simulated Runtime Error');printInfo(`Error: Logic eval error at PC ${failedPc}: int 1 expected bytes`);printInfo('');
if (errorLocation) { const sourceFile = sourceMap.sources[errorLocation.sourceIndex]; const line = errorLocation.line + 1; // Convert to 1-indexed for display const column = errorLocation.column + 1; const symbolName = errorLocation.nameIndex !== undefined ? sourceMap.names[errorLocation.nameIndex] : undefined;
printInfo('Mapped to Source'); printInfo(`File: ${sourceFile}`); printInfo(`Line: ${line}`); printInfo(`Column: ${column}`); if (symbolName) { printInfo(`Symbol: ${symbolName}`); } printInfo(''); printInfo('This allows you to immediately find the source code location'); printInfo('that caused the error, rather than debugging raw TEAL opcodes.');}printSuccess('Sourcemaps enable efficient debugging of compiled TEAL programs');
// ============================================================================// Step 9: Sourcemap with Multiple Sources// ============================================================================printStep(9, 'Sourcemap with Multiple Sources');
printInfo('Sourcemaps can reference multiple source files.');printInfo('This is common when TEAL is generated from multiple modules.');printInfo('');
// Create a multi-source sourcemapconst multiSourceMap = new ProgramSourceMap({ version: 3, sources: ['main.py', 'utils.py', 'constants.py'], names: ['approval', 'helper', 'CONST'], // Mappings that reference different sources: // PC 0 -> source 0 (main.py) // PC 1 -> source 1 (utils.py) // PC 2 -> source 2 (constants.py) // PC 3 -> source 0 (main.py again) mappings: 'AAAA;ACAA;ACAA;AFAA',});
printInfo(`sources: ${JSON.stringify(multiSourceMap.sources)}`);printInfo('');
const multiPcs = multiSourceMap.getPcs();for (const pc of multiPcs) { const location = multiSourceMap.getLocationForPc(pc); if (location) { const sourceName = multiSourceMap.sources[location.sourceIndex]; printInfo(`PC ${pc} -> ${sourceName}`); }}printInfo('');printInfo('Multiple source support allows tracing code back to the correct');printInfo('module in multi-file projects.');printSuccess('Sourcemaps support multi-file projects with source index tracking');
// ============================================================================// Step 10: Version Validation// ============================================================================printStep(10, 'Version Validation');
printInfo('ProgramSourceMap only supports version 3 sourcemaps.');printInfo('');
try { new ProgramSourceMap({ version: 2, // Invalid version sources: ['test.py'], names: [], mappings: 'AAAA', }); printInfo('ERROR: Version 2 should have been rejected');} catch (error) { if (error instanceof Error) { printInfo(`Creating version 2 sourcemap throws: "${error.message}"`); }}printInfo('');printSuccess('Version validation ensures sourcemap compatibility');
// ============================================================================// Step 11: Summary - Debugging Workflow with Sourcemaps// ============================================================================printStep(11, 'Summary - Debugging Workflow with Sourcemaps');
printInfo('Typical debugging workflow with sourcemaps:');printInfo('');printInfo(' 1. Compile your high-level code (PyTeal, etc.) to TEAL');printInfo(' 2. The compiler generates a sourcemap alongside the TEAL');printInfo(' 3. Load the sourcemap: new ProgramSourceMap(sourcemapData)');printInfo(' 4. When an error occurs, get the PC from the error message');printInfo(' 5. Map PC to source: sourceMap.getLocationForPc(pc)');printInfo(' 6. Navigate to the source location to fix the issue');printInfo('');printInfo('For setting breakpoints:');printInfo(' 1. Identify the source file and line');printInfo(' 2. Get PCs: sourceMap.getPcsOnSourceLine(sourceIndex, line)');printInfo(' 3. Set breakpoints at the returned PC values');printInfo('');
printInfo('Key Methods');printInfo(' getPcs() - All PCs with mappings');printInfo(' getLocationForPc(pc) - Source location for PC');printInfo(' getPcsOnSourceLine(srcIdx, line) - PCs on a source line');printInfo('');
printInfo('Key Types');printInfo(' SourceLocation - { line, column, sourceIndex, nameIndex? }');printInfo(' PcLineLocation - { pc, column, nameIndex? }');printInfo('');
printSuccess('ProgramSourceMap enables effective TEAL debugging!');