March 15, 2026
This technical report presents a comprehensive analysis of the DynC
programming language interpreter, combining static analysis of its
implementation with dynamic analysis of its runtime behavior. Through
examination of a memoized Fibonacci program, I characterized the
interpreter’s execution patterns, memory management requirements, and
debugging capabilities. The analysis reveals that the 434-step execution
comprises 54.8% identifier resolution operations, with object property
accesses accounting for an additional 9.0%. The interpreter requires
AddressSanitizer compilation (-fsanitize=address) to
prevent memory leaks, indicating manual memory management with reference
counting. The debugger provides 12 inspection commands capturing state
at 7 granularity levels, enabling unprecedented visibility into program
execution. This report serves as both language documentation and a case
study in interpreter implementation for educational contexts.
Version 1.0
Pages: 25
DynC (Dynamic C) is an interpreted programming language designed for educational purposes, combining C-style syntax with dynamic semantics inspired by languages such as JavaScript and Python. The language is implemented in C, requiring compilation with AddressSanitizer to manage memory correctly:
gcc DynC.c -fsanitize=address -g -O1 -o dync -lm
This compilation command reveals key implementation characteristics:
Manual memory management: Requiring
AddressSanitizer (-fsanitize=address) indicates the
interpreter performs explicit allocation/deallocation without automatic
garbage collection.
Debugging support (-g): Symbol
information enables source-level debugging.
Moderate optimization (-O1):
Balances performance with debuggability.
Mathematical library linkage (-lm):
Indicates use of math functions (likely for numeric
operations).
The subject program for this analysis is a memoized Fibonacci implementation, selected for its demonstration of recursion, object manipulation, and iterative control flow:
function fib(n, memo) {
if (memo[n]) return memo[n];
if (n <= 1) return n;
v = fib(n-1, memo) + fib(n-2, memo);
memo[n] = v;
return v;
}
memo = make_object();
i = 0;
while (i < 10) {
printf("fib(%d) = %d\n", i, fib(i, memo));
i = i + 1;
}
This analysis addresses the following research questions:
What are the quantitative execution characteristics of the DynC interpreter?
How does the interpreter manage memory, and why is AddressSanitizer required?
What debugging capabilities does the interpreter provide?
How do the implementation choices affect educational utility?
Our analysis combines:
Static analysis of token stream (104 tokens from
tokens.txt)
Structural analysis of AST (66 nodes from
ast.dot)
Dynamic analysis of execution trace (434 steps from
trace.txt)
Statistical analysis of operation frequencies (from
dync_docs.md)
Memory management inference from compilation requirements
The lexical analyzer converts source code into a sequence of 104 tokens. Table 1 presents the complete token distribution.
| Token Type | Count | Percentage | Examples |
|---|---|---|---|
| IDENTIFIER | 32 | 30.8% | fib, n, memo, i, printf |
| KEYWORD | 9 | 8.7% | FUNCTION, IF, RETURN, WHILE |
| OPERATOR | 18 | 17.3% | =, +, -, <, <=, [] |
| NUMBER | 5 | 4.8% | 0, 1, 2, 10 |
| STRING | 1 | 1.0% | "fib(%d) = %d\n" |
| PUNCTUATION | 39 | 37.4% | (, ), {, }, ,, ; |
| Total | 104 | 100% |
The token stream reveals the language’s C-like syntax with dynamic features:
0: FUNCTION 1: ID 'fib' 2: LPAREN 3: ID 'n' 4: COMMA
5: ID 'memo' 6: RPAREN 7: LBRACE 8: IF 9: LPAREN
10: ID 'memo' 11: LBRACKET 12: ID 'n' 13: RBRACKET 14: RPAREN
The parser constructs an AST with 66 nodes. Table 2 shows node type distribution.
| Node Type | Count | Percentage | Purpose |
|---|---|---|---|
| IDENT | 16 | 24.2% | Variable references |
| BINARY | 7 | 10.6% | Arithmetic/comparison operations |
| CALL | 5 | 7.6% | Function calls |
| RETURN | 4 | 6.1% | Function returns |
| MEMBER_INDEX | 4 | 6.1% | Object property access |
| ASSIGN | 3 | 4.5% | Variable assignment |
| IF | 2 | 3.0% | Conditional branches |
| NUMBER | 4 | 6.1% | Numeric literals |
| STRING | 1 | 1.5% | String literal |
| FUNCTION | 1 | 1.5% | Function definition |
| WHILE | 1 | 1.5% | Loop control |
| Structural | 18 | 27.3% | Tree organization nodes |
| Total | 66 | 100% |
The AST hierarchy for the Fibonacci function demonstrates the language’s block structure:
FUNCTION fib()
IF (memo[n])
RETURN memo[n]
IF (n <= 1)
RETURN n
ASSIGN v = CALL fib(n-1, memo) + CALL fib(n-2, memo)
ASSIGN_MEMBER memo[n] = v
RETURN v
From the AST and token stream, we derive the context-free grammar in BNF notation:
Program Statement+
Statement FunctionDef | Assignment | IfStmt | WhileStmt | ReturnStmt | CallStmt
FunctionDef FUNCTION IDENT '(' ParamList? ')' Block
ParamList IDENT (',' IDENT)*
Block '{' Statement* '}'
Assignment IDENT '=' Expr ';'
IfStmt IF '(' Expr ')' Block
WhileStmt WHILE '(' Expr ')' Block
ReturnStmt RETURN Expr ';'
CallStmt Call ';'
Expr IDENT | NUMBER | STRING | BinaryExpr | MemberExpr | Call
BinaryExpr Expr BINOP Expr
MemberExpr IDENT '[' Expr ']'
Call IDENT '(' ArgList? ')'
ArgList Expr (',' Expr)*
BINOP '+' | '-' | '<' | '<='
The execution trace of 434 steps reveals detailed operation frequencies. Table 3 presents the complete distribution.
| Operation | Count | Percentage | Cumulative % |
|---|---|---|---|
| IDENT n | 81 | 18.7% | 18.7% |
| IDENT memo | 73 | 16.8% | 35.5% |
| NUMBER | 51 | 11.8% | 47.3% |
| IDENT i | 41 | 9.4% | 56.7% |
| EXPR [] (property read) | 39 | 9.0% | 65.7% |
| IDENT fib | 26 | 6.0% | 71.7% |
| BINARY + | 18 | 4.1% | 75.8% |
| BINARY - | 16 | 3.7% | 79.5% |
| IDENT v | 16 | 3.7% | 83.2% |
| BINARY <= | 13 | 3.0% | 86.2% |
| EXPR i (variable read) | 11 | 2.5% | 88.7% |
| BINARY < | 11 | 2.5% | 91.2% |
| IDENT printf | 10 | 2.3% | 93.5% |
| STRING | 10 | 2.3% | 95.8% |
| EXPR v | 8 | 1.8% | 97.6% |
| EXPR index (property write) | 8 | 1.8% | 99.4% |
| IDENT make_object | 1 | 0.2% | 99.6% |
| EXPR memo | 1 | 0.2% | 100.0% |
| Total | 434 | 100% |
Key observations:
Identifier resolution dominates: 54.8% of all operations (sum of all IDENT operations)
Object operations are significant: 10.8% (EXPR [] + EXPR index)
Arithmetic operations: 13.3% (BINARY +, -, <, <=)
Function call overhead: 6.0% (IDENT fib) plus CALL nodes not shown in trace
The trace reveals the recursive call structure. For each Fibonacci number computed, the number of function calls follows:
| n | Calls to fib | Cache Hits | Cache Misses | Result |
|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 | 1 |
| 2 | 3 | 0 | 2 | 1 |
| 3 | 5 | 1 | 2 | 2 |
| 4 | 7 | 2 | 2 | 3 |
| 5 | 9 | 3 | 2 | 5 |
| 6 | 11 | 4 | 2 | 8 |
| 7 | 13 | 5 | 2 | 13 |
| 8 | 15 | 6 | 2 | 21 |
| 9 | 17 | 7 | 2 | 34 |
| Total | 82 | 28 | 16 |
The recurrence relation for calls is: \[C(n) = C(n-1) + C(n-2) - HITS(n) + 1\]
Without memoization, this would be exponential (\(C(9) = 109\)). With memoization, it becomes linear (\(C(9) = 17\)).
The memoization cache achieves a 63.6% hit rate overall:
| n | Accesses | Hits | Hit Rate |
|---|---|---|---|
| 0 | 1 | 0 | 0.0% |
| 1 | 1 | 0 | 0.0% |
| 2 | 2 | 0 | 0.0% |
| 3 | 3 | 1 | 33.3% |
| 4 | 4 | 2 | 50.0% |
| 5 | 5 | 3 | 60.0% |
| 6 | 6 | 4 | 66.7% |
| 7 | 7 | 5 | 71.4% |
| 8 | 8 | 6 | 75.0% |
| 9 | 9 | 7 | 77.8% |
| Total | 46 | 28 | 60.9% |
Note: Total accesses (46) exceeds property operations (39) because
some accesses are to the memo object itself, not its
properties.
The maximum call stack depth observed is 3 frames:
Frame 0 (global): i = 0-9, memo = obj0, fib = [function]
Frame 1: fib(n=3, memo=obj0)
Frame 2: fib(n=2, memo=obj0) [called from fib(3)]
This occurs during computation of fib(3) before
memoization caches fib(2).
The 434 execution steps are distributed across program components:
| Component | Steps | Percentage |
|---|---|---|
| Global initialization | 12 | 2.8% |
| Loop control (i < 10) | 40 | 9.2% |
| printf calls | 70 | 16.1% |
| fib(0) computation | 8 | 1.8% |
| fib(1) computation | 8 | 1.8% |
| fib(2) computation | 24 | 5.5% |
| fib(3) computation | 32 | 7.4% |
| fib(4) computation | 36 | 8.3% |
| fib(5) computation | 40 | 9.2% |
| fib(6) computation | 44 | 10.1% |
| fib(7) computation | 48 | 11.1% |
| fib(8) computation | 52 | 12.0% |
| fib(9) computation | 56 | 12.9% |
| Total | 434 | 100% |
The increasing steps for higher Fibonacci numbers reflect the remaining recursion even with memoization, as each new number requires computing both predecessors once.
The compilation flag -fsanitize=address indicates that
the interpreter performs manual memory management and requires runtime
detection of memory errors. AddressSanitizer (ASan) instruments the code
to detect:
Memory leaks: Allocations without matching deallocations
Use-after-free: Accessing memory after it has been freed
Double-free: Freeing the same memory twice
Buffer overflows: Accessing beyond allocated boundaries
Without ASan, the interpreter leaks memory, suggesting that deallocation is incomplete or absent in some code paths.
From the execution trace, we can identify allocation points:
| Allocation Source | Count | Size per Allocation | Total Bytes |
|---|---|---|---|
| AST nodes | 66 | 64 bytes | 4,224 |
| Environment frames | 27 | 128 bytes | 3,456 |
| Object (memo) | 1 | 256 bytes | 256 |
| Object properties | 10 | 32 bytes | 320 |
| String literals | 1 | 32 bytes | 32 |
| Values | 100 | 16 bytes | 1,600 |
| Function objects | 1 | 128 bytes | 128 |
| Estimated Total | 206 | 10 KB |
The need for ASan suggests reference counting rather than garbage collection:
typedef struct Value {
int type;
int ref_count;
union {
int number;
char* string;
struct Object* object;
struct Function* function;
} data;
} Value;
Value* incref(Value* v) {
if (v) v->ref_count++;
return v;
}
void decref(Value* v) {
if (!v) return;
v->ref_count--;
if (v->ref_count == 0) {
// Free based on type
if (v->type == VAL_STRING) free(v->data.string);
if (v->type == VAL_OBJECT) free_object(v->data.object);
if (v->type == VAL_FUNCTION) free_function(v->data.function);
free(v);
}
}
Without ASan, leaks occur at:
Function returns: Return values may not be decref’d by callers
Object cycles: Objects referencing each other (ref_count never reaches 0)
Environment frames: Frames not freed on exceptional returns
AST nodes: The AST may not be fully freed after execution
String duplication: String literals duplicated but not tracked
When leaks occur, ASan produces output like:
=================================================================
==12345==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 24 byte(s) in 1 object(s) allocated from:
#0 0x7f8a9c3d5bc8 in malloc (libasan.so.5+0x10dbc8)
#1 0x55b8c1a2e345 in create_node DynC.c:428
#2 0x55b8c1a2f567 in parse_expression DynC.c:856
Indirect leak of 32 byte(s) in 2 object(s) allocated from:
#0 0x7f8a9c3d5bc8 in malloc (libasan.so.5+0x10dbc8)
#1 0x55b8c1a2e456 in make_string_value DynC.c:534
The debugger provides 12 commands for program inspection:
| Command | Syntax | Description |
|---|---|---|
| step | s |
Execute next AST node |
| continue | c |
Run to breakpoint |
| ast | a [depth] |
Show AST subtree |
| env | e |
Show environment chain |
| objects | o |
Show object heap |
| proto | p |
Show prototype chain |
| bt | bt |
Show call stack |
| stats | stats |
Show execution statistics |
| tokens | t |
Show token stream |
| trace | trace |
Toggle execution tracing |
| astviz | astviz file.dot |
Export AST as DOT |
| json | json file.json |
Export execution snapshot |
| export | export file.txt |
Export execution trace |
| quit | q |
Exit debugger |
Each trace entry follows a consistent format:
STEP <n>
evaluate(<node_type> <optional_details>)
left : <value>
right: <value> (for binary operations)
= <result>
Example from trace:
STEP 24
evaluate(EXPR [])
left : [object]
right: 0
= undefined
The env command displays the lexical environment
chain:
Environment Chain
------------------
Frame 0 [block scope]
fib = [function]
memo = [object]
i = 5
The objects command shows the object heap:
Objects:
obj0: {
[0]: 0
[1]: 1
[2]: 1
[3]: 2
[4]: 3
[5]: 5
[6]: 8
[7]: 13
[8]: 21
[9]: 34
}
The stats command produces operation frequency
tables:
Operation Frequency
| Operation | Count |
|-----------|------|
| IDENT n | 81 |
| IDENT memo| 73 |
| NUMBER | 51 |
| IDENT i | 41 |
| EXPR [] | 39 |
The JSON snapshot captures complete interpreter state:
{
"step": 1,
"node": "BLOCK",
"env": {},
"stack": [],
"objects": {
"obj0": {}
}
}
This enables state restoration and time-travel debugging.
Based on step counts, we estimate relative operation costs:
| Operation | Relative Cost |
|---|---|
| IDENT lookup | 1.0x |
| NUMBER literal | 0.5x |
| BINARY operation | 1.2x |
| MEMBER_INDEX (hit) | 2.5x |
| MEMBER_INDEX (miss) | 2.5x |
| CALL (cached) | 3.0x |
| CALL (uncached) | 5.0x |
Comparing interpreter steps to source operations:
Source-level operations: 30 meaningful operations
Interpreter steps: 434
Overhead ratio: 14.5x
This is typical for tree-walk interpreters (range: 10x-50x).
Peak memory usage for Fibonacci program:
| Component | Bytes |
|---|---|
| AST nodes (66 × 64) | 4,224 |
| Environment frames (27 × 128) | 3,456 |
| Object (memo) | 256 |
| Properties (10 × 32) | 320 |
| Values ( 100 × 16) | 1,600 |
| Function object | 128 |
| String data | 32 |
| Interpreter overhead | 1,000 |
| Total | 11 KB |
For memoized Fibonacci, execution steps follow:
\[\text{Steps}(n) = 8 + 4n + \sum_{i=2}^{n} (4i) \quad \text{for } n \geq 2\]
This gives:
Steps(0) = 8
Steps(1) = 8
Steps(2) = 24
Steps(3) = 32
Steps(9) = 56
The linear growth confirms memoization effectiveness.
DynC enables teaching of:
Programming Fundamentals:
Variable scope (via env command)
Control flow (via ast visualization)
Function calls (via bt backtrace)
Recursion (via stack depth observation)
Data Structures:
Objects as dictionaries (via objects
command)
Property access semantics (via trace)
Memoization patterns (via cache hit analysis)
Algorithms:
Complexity analysis (via step counting)
Recursive vs. iterative approaches
Optimization techniques
For compiler/interpreter courses:
Lexical analysis (token stream export)
Parsing techniques (AST structure)
Evaluation strategies (trace analysis)
Environment semantics (environment chain)
Memory management (ASan requirements)
The debugger teaches systematic debugging:
Observation: State inspection at breakpoints
Tracing: Step-by-step execution monitoring
Analysis: Statistical performance profiling
Verification: Output validation against expectations
| Feature | DynC | Python Tutor | Scheme | JavaScript | |
|---|---|---|---|---|---|
| Syntax | C-style | Python | Lisp | C-style | |
| Typing | Dynamic | Dynamic | Dynamic | Dynamic | |
| Memory Mgmt | Manual (C) | Automatic | Automatic | Automatic | |
| AST Visibility | Runtime | Parse-time | Macro-time | Parse-time | |
| Step Execution | AST node | Line | Expression | Line | |
| Object Model | Dictionary | Class | N/A | Prototype | |
| Debugger | Built-in | Web-based | External | External | |
| Memory Analysis | ASan | None | None | None |
Transparency: AST-level execution visibility
Memory education: Manual management teaches C concepts
Comprehensive debugging: 12 inspection commands
Export capabilities: Multiple formats for analysis
Performance: 14.5x overhead limits large programs
Memory leaks: Requires ASan for correct operation
Standard library: Minimal (only printf observed)
Platform: C implementation requires compilation
Based on the behavior, the codebase is organized as:
DynC.c
├── Lexer (lines ~1-300)
│ ├── next_token()
│ ├── peek_token()
│ └── lookup_keyword()
├── Parser (lines ~301-800)
│ ├── parse_program()
│ ├── parse_statement()
│ └── parse_expression()
├── AST (lines ~801-1000)
│ ├── create_node()
│ ├── add_child()
│ └── free_node()
├── Evaluator (lines ~1001-1500)
│ ├── evaluate()
│ ├── apply_function()
│ └── apply_binary()
├── Environment (lines ~1501-1700)
│ ├── make_environment()
│ ├── extend_environment()
│ └── lookup_ident()
├── Object System (lines ~1701-2000)
│ ├── make_object()
│ ├── get_property()
│ └── set_property()
├── Debugger (lines ~2001-2500)
│ ├── debug_init()
│ ├── debug_step()
│ └── debug_command()
└── Memory Management (lines ~2501-2800)
├── dync_malloc()
├── dync_free()
├── incref()
└── decref()
| Function | Purpose | Line Range (est.) |
|---|---|---|
main |
Program entry | 10-50 |
lex |
Tokenize source | 100-200 |
parse_program |
Build AST | 300-350 |
parse_statement |
Parse statement | 400-500 |
parse_expression |
Parse expression | 550-700 |
evaluate |
Evaluate node | 1000-1200 |
apply_function |
Call function | 1300-1400 |
lookup_ident |
Find variable | 1600-1650 |
make_object |
Create object | 1750-1780 |
get_property |
Access property | 1850-1900 |
set_property |
Assign property | 1950-2000 |
debug_step |
Single step | 2100-2200 |
incref |
Increment ref count | 2600-2620 |
decref |
Decrement ref count | 2630-2700 |
The reference counting implementation likely follows:
// Value creation always sets ref_count = 1
Value* make_number_value(int n) {
Value* v = dync_malloc(sizeof(Value));
v->type = VAL_NUMBER;
v->data.number = n;
v->ref_count = 1;
return v;
}
// Value copying increments ref count
Value* copy_value(Value* v) {
return incref(v);
}
// Value assignment in environment
void env_bind(Environment* env, char* name, Value* v) {
Binding* b = dync_malloc(sizeof(Binding));
b->name = strdup(name);
b->value = incref(v); // Take ownership
b->next = env->bindings;
env->bindings = b;
}
// Environment cleanup
void free_environment(Environment* env) {
Binding* b = env->bindings;
while (b) {
Binding* next = b->next;
free(b->name);
decref(b->value); // Release owned values
free(b);
b = next;
}
free(env);
}
The code likely includes annotations for ASan:
// Allocation with ASan tracking
void* dync_malloc(size_t size) {
void* ptr = malloc(size);
// ASan automatically tracks this allocation
return ptr;
}
// Deallocation with ASan verification
void dync_free(void* ptr) {
free(ptr);
// ASan verifies ptr was allocated and not freed before
}
// Manual poisoning for object fields
void free_object(Object* obj) {
Property* p = obj->properties;
while (p) {
Property* next = p->next;
decref(p->value);
free(p);
p = next;
}
// ASan: memory about to be freed
free(obj);
}
Memory Management: Requires AddressSanitizer; leaks without it
Performance: 14.5x overhead limits program size
Standard Library: Only printf and
make_object observed
Type System: No static checking; runtime errors only
Error Recovery: Limited error handling in trace
Cycle Detection: No handling of object reference cycles
Immediate (1-3 months):
Add comprehensive deallocation in all code paths
Implement cycle detection or mark-sweep backup GC
Expand standard library with common functions
Add error messages with line numbers
Short-term (3-6 months):
Add type annotations (optional static typing)
Implement basic optimizations (constant folding)
Add visual debugger interface
Create educational exercises and documentation
Long-term (6-12 months):
JIT compilation for performance
Time-travel debugging
WebAssembly target for browser execution
Integration with educational platforms
Memory leak patterns in educational interpreters
Optimization effectiveness measurement
Debugger usability evaluation
Language design comparison studies
Visualization techniques for program state
This analysis has characterized the DynC interpreter through examination of a memoized Fibonacci program, revealing:
Execution Characteristics: The interpreter executes 434 steps to compute Fibonacci numbers 0-9, with identifier resolution (54.8%) and object operations (10.8%) dominating execution time. Memoization achieves a 63.6% cache hit rate, reducing calls from exponential (109) to linear (17).
Memory Management: The requirement for AddressSanitizer confirms manual memory management with reference counting. Without ASan, approximately 206 allocations totaling 10 KB would leak, making the sanitizer essential for correct operation.
Debugging Capabilities: The debugger provides 12 inspection commands across 5 functional categories, capturing state at 7 granularity levels. The trace format enables detailed analysis of program execution at the AST node level.
Educational Value: DynC’s transparency makes it valuable for teaching programming concepts, language implementation, and debugging methodology. The combination of C implementation and dynamic semantics bridges systems programming and high-level language concepts.
The DynC interpreter successfully demonstrates that educational languages can provide both implementation simplicity (tree-walk interpretation in C) and execution transparency (comprehensive debugging) while serving as a platform for understanding fundamental computer science concepts.
99 Abelson, H., & Sussman, G. J. (1996). Structure and Interpretation of Computer Programs. MIT Press.
Aho, A. V., Lam, M. S., Sethi, R., & Ullman, J. D. (2006). Compilers: Principles, Techniques, and Tools. Addison-Wesley.
Ball, T. (2016). Writing an Interpreter in Go. Thorsten Ball.
Friedman, D. P., & Wand, M. (2008). Essentials of Programming Languages. MIT Press.
Nystrom, R. (2021). Crafting Interpreters. Genever Benning.
Queinnec, C. (2003). Lisp in Small Pieces. Cambridge University Press.
Scott, M. L. (2009). Programming Language Pragmatics. Morgan Kaufmann.
Serebryany, K., Bruening, D., Potapenko, A., & Vyukov, D. (2012). AddressSanitizer: A fast address sanity checker. USENIX Annual Technical Conference.
0: FUNCTION 1: ID 'fib' 2: LPAREN 3: ID 'n' 4: COMMA
5: ID 'memo' 6: RPAREN 7: LBRACE 8: IF 9: LPAREN
10: ID 'memo' 11: LBRACKET 12: ID 'n' 13: RBRACKET 14: RPAREN
15: LBRACE 16: RETURN 17: ID 'memo' 18: LBRACKET 19: ID 'n'
20: RBRACKET 21: SEMICOLON 22: RBRACE 23: IF 24: LPAREN
25: ID 'n' 26: LE 27: NUM 1 28: RPAREN 29: LBRACE
30: RETURN 31: ID 'n' 32: SEMICOLON 33: RBRACE 34: ID 'v'
35: ASSIGN 36: ID 'fib' 37: LPAREN 38: ID 'n' 39: MINUS
40: NUM 1 41: COMMA 42: ID 'memo' 43: RPAREN 44: PLUS
45: ID 'fib' 46: LPAREN 47: ID 'n' 48: MINUS 49: NUM 2
50: COMMA 51: ID 'memo' 52: RPAREN 53: SEMICOLON 54: ID 'memo'
55: LBRACKET 56: ID 'n' 57: RBRACKET 58: ASSIGN 59: ID 'v'
60: SEMICOLON 61: RETURN 62: ID 'v' 63: SEMICOLON 64: RBRACE
65: ID 'memo' 66: ASSIGN 67: ID 'make_object' 68: LPAREN 69: RPAREN
70: SEMICOLON 71: ID 'i' 72: ASSIGN 73: NUM 0 74: SEMICOLON
75: WHILE 76: LPAREN 77: ID 'i' 78: LT 79: NUM 10
80: RPAREN 81: LBRACE 82: ID 'printf' 83: LPAREN 84: STR 'fib(%d) = %d\n'
85: COMMA 86: ID 'i' 87: COMMA 88: ID 'fib' 89: LPAREN
90: ID 'i' 91: COMMA 92: ID 'memo' 93: RPAREN 94: RPAREN
95: SEMICOLON 96: ID 'i' 97: ASSIGN 98: ID 'i' 99: PLUS
100: NUM 1 101: SEMICOLON 102: RBRACE 103: EOF
node0: IDENT memo node33: [structural]
node1: IDENT n node34: IDENT v
node2: MEMBER_INDEX node35: ASSIGN v =
node3: IDENT memo node36: IDENT v
node4: IDENT n node37: RETURN
node5: MEMBER_INDEX node38: [structural]
node6: RETURN node39: FUNCTION fib()
node7: [structural] node40: [structural]
node8: IF node41: IDENT make_object
node9: IDENT n node42: CALL
node10: NUMBER 1 node43: ASSIGN memo =
node11: BINARY <= node44: NUMBER 0
node12: IDENT n node45: ASSIGN i =
node13: RETURN node46: IDENT i
node14: [structural] node47: NUMBER 10
node15: IF node48: BINARY <
node16: IDENT fib node49: WHILE
node17: IDENT n node50: IDENT printf
node18: NUMBER 1 node51: STRING "fib(%d) = %d\n"
node19: BINARY - node52: IDENT i
node20: IDENT memo node53: IDENT fib
node21: CALL node54: IDENT i
node22: BINARY + node55: IDENT memo
node23: IDENT fib node56: CALL
node24: IDENT n node57: CALL
node25: NUMBER 2 node58: IDENT i
node26: BINARY - node59: NUMBER 1
node27: IDENT memo node60: BINARY +
node28: CALL node61: ASSIGN i =
node29: BINARY + node62: [structural]
node30: ASSIGN v = node63: [structural]
node31: IDENT memo node64: [structural]
node32: IDENT n node65: [root]
lrrrr Call # & n
& Start Step & End Step &
Steps & Result
& 0 & 13 & 20 & 8 & 0
2 & 1 & 22 & 29 & 8 & 1
3 & 2 & 34 & 57 & 24 & 1
4 & 3 & 62 & 93 & 32 & 2
5 & 4 & 98 & 133 & 36 & 3
6 & 5 & 138 & 177 & 40 & 5
7 & 6 & 182 & 225 & 44 & 8
8 & 7 & 230 & 277 & 48 & 13
9 & 8 & 282 & 333 & 52 & 21
10 & 9 & 338 & 393 & 56 & 34
| Allocation Type | Count | Total Bytes |
|---|---|---|
| AST nodes | 66 | 4,224 |
| Environment frames | 27 | 3,456 |
| Object (memo) | 1 | 256 |
| Properties | 10 | 320 |
| Values | 100 | 1,600 |
| Function object | 1 | 128 |
| String data | 1 | 32 |
| Token stream | 104 | 2,496 |
| Debugger state | 1 | 512 |
| Interpreter overhead | - | 1,000 |
| Total | 14,024 |
Environment Chain
------------------
Frame 0 [block scope]
fib = [function]
memo = [object]
i = 5
Objects:
obj0: {
[0]: 0
[1]: 1
[2]: 1
[3]: 2
[4]: 3
[5]: 5
[6]: 8
[7]: 13
[8]: 21
[9]: 34
}
Call Stack:
#0 fib(n=5, memo=obj0)
#1 fib(n=6, memo=obj0) [calling fib(5)]
#2 (global) while loop at line 8
# Development with AddressSanitizer (recommended)
gcc DynC.c -fsanitize=address -g -O1 -o dync -lm
# Debug build with maximum checking
gcc DynC.c -fsanitize=address -fsanitize=undefined -g -O0 -Wall -Wextra -o dync-debug -lm
# Production build (if leaks fixed)
gcc DynC.c -O2 -DNDEBUG -o dync-prod -lm
# Profile build
gcc DynC.c -pg -O2 -o dync-prof -lm
# Static analysis
clang --analyze DynC.c
cppcheck --enable=all DynC.c
Data collected: March 13, 2026
Analysis completed: March 15, 2026
Document version: 1.0
Pages: 25