Skip to content
Algorand Developer Portal

Sourcemap

← Back to Common Utilities

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
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example common/12-sourcemap.ts

View source on GitHub

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 PC
for (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 exist
const 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 source
const 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 scenario
const 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 sourcemap
const 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!');