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 = 999const 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 = 0const 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 = 1const 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!')