Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b7ba43b
Add shared AST transformer infrastructure (Phase 1)
fglock Mar 9, 2026
8002a2a
Add ContextResolver pass and wire transformer into pipeline
fglock Mar 9, 2026
91c3497
Add context visualization to --parse output, run transformer earlier
fglock Mar 9, 2026
154e454
Remove duplicate transformer call from EmitterMethodCreator
fglock Mar 9, 2026
1e1b076
Safe acceptChild migration: warn on mismatch, use fallback
fglock Mar 9, 2026
65cd9de
Migrate EmitControlFlow acceptChild calls, fix return context
fglock Mar 9, 2026
b8fd0fc
Update design doc with safe migration progress
fglock Mar 9, 2026
20de4a9
Complete acceptChild migration for all Emit*.java files
fglock Mar 9, 2026
02151e1
Update design doc: Phase 2a complete (136 call sites migrated)
fglock Mar 9, 2026
19acec6
Fix ContextResolver: keys/values/each operand uses LIST context
fglock Mar 9, 2026
0e591e3
Fix ContextResolver: handle map/grep/sort and reference operator cont…
fglock Mar 9, 2026
e44ad67
Fix slice subscript context: use LIST for @a[list], %h{keys}
fglock Mar 9, 2026
60668a5
Update design doc: all context mismatches fixed
fglock Mar 9, 2026
210c0ad
Fix interpreter: don't use cached context until ContextResolver is co…
fglock Mar 9, 2026
6cc8160
WIP: acceptChild using cached context (tests failing)
fglock Mar 9, 2026
9aa4e53
Unify context annotation system: use cachedContext throughout
fglock Mar 9, 2026
6a731c5
Update design doc with unified context annotation work
fglock Mar 9, 2026
fe5e6f9
Fix ContextResolver: add sprintf/all/any BinaryOperatorNode handlers
fglock Mar 9, 2026
15d5c5e
Fix ContextResolver: add more BinaryOperatorNode handlers
fglock Mar 9, 2026
da12d16
Update design doc: BinaryOperatorNode fixes reduce mismatches by 67%
fglock Mar 9, 2026
2d243b1
Fix ContextResolver: undef and push/unshift context handlers
fglock Mar 9, 2026
4a26d6e
Update design doc: Phase 2a complete with minimal mismatches
fglock Mar 9, 2026
53b551c
Migrate Dereference.java to use acceptChild for context tracking
fglock Mar 9, 2026
fbb623b
Fix ContextResolver: propagate SCALAR context through subscript literals
fglock Mar 9, 2026
d6bd798
Update design doc: Dereference.java migration complete
fglock Mar 9, 2026
c43e5c6
Fix remaining context mismatches in ContextResolver
fglock Mar 9, 2026
28d0530
Revert c43e5c68 context changes that broke Writer.t and QuickTime.t
fglock Mar 9, 2026
35dadc9
Fix ContextResolver: set SCALAR context for scalar-producing operators
fglock Mar 9, 2026
c105afd
Fix ContextResolver: add setContext for subscript nodes
fglock Mar 9, 2026
51e66c4
Update shared-ast-transformer skill: add interpreter migration guide
fglock Mar 9, 2026
f90a798
Skill update: context must be 100% identical - no exceptions
fglock Mar 9, 2026
3ce9078
Skill update: add detailed methodology for 100% context accuracy
fglock Mar 9, 2026
9ac2782
Fix ExifTool context mismatches: subscripts, arrow deref, array literals
fglock Mar 9, 2026
e95f1b8
Migrate interpreter to use cached context from ContextResolver
fglock Mar 10, 2026
bedd2e8
Fix interpreter: print/say filehandle should use SCALAR context
fglock Mar 10, 2026
68d9a40
Fix interpreter context mismatches for concat and assignments
fglock Mar 10, 2026
e4108f2
Update design doc: interpreter context fixes progress
fglock Mar 10, 2026
328f1b5
Switch to precomputed context for nodes with no mismatches
fglock Mar 10, 2026
c49bada
Update design doc: selective context switching details
fglock Mar 10, 2026
7a89b19
Extend hasKnownMismatch lists for both backends
fglock Mar 10, 2026
0c885d8
Update design doc with current mismatch list status
fglock Mar 10, 2026
48622a4
Fix scalar operator context: inherit parent context
fglock Mar 10, 2026
8a98c6e
Remove backslash operator from interpreter mismatch list
fglock Mar 10, 2026
03af658
Update design doc: document remaining context mismatches
fglock Mar 10, 2026
caf4202
Trim mismatch lists to only actual mismatches
fglock Mar 10, 2026
e5346f8
Add acceptChild() overload for precomputed context only
fglock Mar 10, 2026
3728dc3
Remove interpreter mismatch workaround - trust ContextResolver
fglock Mar 10, 2026
b1a6fe4
JVM emitter: use fallback context on mismatch to avoid ASM crashes
fglock Mar 10, 2026
2253eaa
Update design doc: document mismatch handling changes
fglock Mar 10, 2026
c9f278f
Clean up debug code from EmitterVisitor
fglock Mar 10, 2026
590a489
Fix ContextResolver: set SCALAR context for non-slice subscript left …
fglock Mar 10, 2026
283a3ba
Fix ContextResolver: set LIST context for slice left operands
fglock Mar 10, 2026
5878a7a
Update design doc: context mismatch progress (17 -> 14)
fglock Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
719 changes: 719 additions & 0 deletions .cognition/skills/shared-ast-transformer/SKILL.md

Large diffs are not rendered by default.

358 changes: 339 additions & 19 deletions dev/design/shared_ast_transformer.md

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions dev/tools/analyze_context_calls.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env perl
# Analyze acceptChild calls in emitter to extract context expectations
use strict;
use warnings;

my %patterns;
my %by_file;

while (<>) {
# Parse: file:line: emitterVisitor.acceptChild(node.field, RuntimeContextType.CONTEXT);
if (/^([^:]+):(\d+):\s*.*acceptChild\(([^,]+),\s*RuntimeContextType\.(\w+)\)/) {
my ($file, $line, $node_expr, $context) = ($1, $2, $3, $4);
$file =~ s|.*/||; # basename

# Normalize node expression
my $pattern = $node_expr;
$pattern =~ s/\s+//g;

push @{$patterns{$pattern}{$context}}, "$file:$line";
$by_file{$file}++;
}
}

print "=== Context expectations by node expression ===\n\n";
for my $pattern (sort keys %patterns) {
my $contexts = $patterns{$pattern};
my @ctx_list = sort keys %$contexts;

if (@ctx_list == 1) {
# Consistent context
my $ctx = $ctx_list[0];
my $count = scalar @{$contexts->{$ctx}};
print "$pattern => $ctx ($count calls)\n";
} else {
# Multiple contexts - needs special handling
print "*** $pattern => VARIES:\n";
for my $ctx (@ctx_list) {
my $locs = $contexts->{$ctx};
print " $ctx: " . join(", ", @$locs) . "\n";
}
}
}

print "\n=== Calls by file ===\n";
for my $file (sort { $by_file{$b} <=> $by_file{$a} } keys %by_file) {
print "$file: $by_file{$file}\n";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.perlonjava.backend.jvm.EmitterContext;
import org.perlonjava.backend.jvm.EmitterMethodCreator;
import org.perlonjava.backend.jvm.JavaClassInfo;
import org.perlonjava.frontend.analysis.ASTTransformer;
import org.perlonjava.frontend.astnode.Node;
import org.perlonjava.frontend.lexer.Lexer;
import org.perlonjava.frontend.lexer.LexerToken;
Expand Down Expand Up @@ -165,6 +166,10 @@ public static RuntimeList executePerlCode(CompilerOptions compilerOptions,

// ast = ConstantFoldingVisitor.foldConstants(ast);

// Run shared AST transformer to compute context annotations
// (idempotent - skips if already transformed)
ASTTransformer.createDefault().transform(ast);

if (ctx.compilerOptions.parseOnly) {
// Printing the ast
System.out.println(ast);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public class BytecodeCompiler implements Visitor {

// Track current calling context for subroutine calls
int currentCallContext = RuntimeContextType.LIST; // Default to LIST

// Context mismatch tracking (for migration validation)
static final Map<String, java.util.concurrent.atomic.AtomicInteger> interpreterContextMismatches =
new java.util.concurrent.ConcurrentHashMap<>();
Map<String, Integer> capturedVarIndices; // Name → register index
// BEGIN support for named subroutine closures
int currentSubroutineBeginId = 0; // BEGIN ID for current named subroutine (0 = not in named sub)
Expand Down Expand Up @@ -834,12 +838,8 @@ public void visit(BlockNode node) {

boolean isLastStatement = (i == lastMeaningfulIndex);
int stmtTarget = (isLastStatement && outerResultReg >= 0) ? outerResultReg : -1;
int stmtContext;
if (!isLastStatement && !(stmt instanceof BinaryOperatorNode && ((BinaryOperatorNode) stmt).operator.equals("="))) {
stmtContext = RuntimeContextType.VOID;
} else {
stmtContext = currentCallContext;
}
// Non-last statements use VOID context (matches JVM emitter)
int stmtContext = isLastStatement ? currentCallContext : RuntimeContextType.VOID;

compileNode(stmt, stmtTarget, stmtContext);

Expand Down Expand Up @@ -3732,15 +3732,81 @@ void emitAliasWithTarget(int destReg, int srcReg) {
}
}

/**
* Compiles a node using its precomputed context from ContextResolver.
* Use this when the node's context was set by ContextResolver.
*/
void compileNode(Node node) {
if (node == null) return;
int savedTarget = targetOutputReg;
int savedContext = currentCallContext;
targetOutputReg = -1;

if (node instanceof AbstractNode an && an.hasCachedContext()) {
currentCallContext = an.getCachedContext();
}
// else keep current context as fallback

node.accept(this);
targetOutputReg = savedTarget;
currentCallContext = savedContext;
}

/**
* Compiles a node with explicit fallback context.
* Uses cached context when available, falls back to specified context otherwise.
*/
void compileNode(Node node, int targetReg, int callContext) {
int savedTarget = targetOutputReg;
int savedContext = currentCallContext;
targetOutputReg = targetReg;
currentCallContext = callContext;

// Use cached context when available, track mismatches for debugging
int contextToUse = callContext;
if (node instanceof AbstractNode an && an.hasCachedContext()) {
int cached = an.getCachedContext();
if (cached != callContext) {
String key = nodeDescription(node) + " cached=" + contextName(cached) + " expected=" + contextName(callContext);
var counter = interpreterContextMismatches.computeIfAbsent(key, k -> new java.util.concurrent.atomic.AtomicInteger());
counter.incrementAndGet();
}
// Always use cached context - ContextResolver is authoritative
contextToUse = cached;
}

currentCallContext = contextToUse;
node.accept(this);
targetOutputReg = savedTarget;
currentCallContext = savedContext;
}

private String nodeDescription(Node node) {
if (node instanceof OperatorNode op) return "OperatorNode(" + op.operator + ")";
if (node instanceof BinaryOperatorNode bin) return "BinaryOperatorNode(" + bin.operator + ")";
return node.getClass().getSimpleName();
}

private String contextName(int ctx) {
return switch (ctx) {
case RuntimeContextType.VOID -> "VOID";
case RuntimeContextType.SCALAR -> "SCALAR";
case RuntimeContextType.LIST -> "LIST";
case RuntimeContextType.RUNTIME -> "RUNTIME";
default -> "UNKNOWN(" + ctx + ")";
};
}

// Mismatch list removed - ContextResolver is authoritative
// If mismatches cause test failures, fix ContextResolver, not this list

public static void printInterpreterMismatches() {
if (interpreterContextMismatches.isEmpty()) return;
System.err.println("\n=== Interpreter Context Mismatches (vs ContextResolver) ===");
interpreterContextMismatches.entrySet().stream()
.sorted((a, b) -> b.getValue().get() - a.getValue().get())
.forEach(e -> System.err.println(e.getKey() + " : " + e.getValue().get() + " times"));
System.err.println();
}

// =========================================================================
// HELPER METHODS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ static void visitBinaryOperator(BytecodeCompiler bytecodeCompiler, BinaryOperato
// left = filehandle reference (\*STDERR)
// right = list to print

bytecodeCompiler.compileNode(node.left, -1, bytecodeCompiler.currentCallContext);
// Filehandle is evaluated in SCALAR context (matches ContextResolver and JVM emitter)
bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR);
int filehandleReg = bytecodeCompiler.lastResultReg;

// Compile the content (right operand) in LIST context
Expand Down Expand Up @@ -238,7 +239,9 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
// Convert class name to string if needed: Class->method()
if (invocantNode instanceof IdentifierNode) {
String className = ((IdentifierNode) invocantNode).name;
invocantNode = new StringNode(className, invocantNode.getIndex());
invocantNode = AbstractNode.withContext(
new StringNode(className, invocantNode.getIndex()),
RuntimeContextType.SCALAR);
}

// Convert method name to string if needed
Expand All @@ -250,7 +253,9 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
}
if (methodNode instanceof IdentifierNode) {
String methodName = ((IdentifierNode) methodNode).name;
methodNode = new StringNode(methodName, methodNode.getIndex());
methodNode = AbstractNode.withContext(
new StringNode(methodName, methodNode.getIndex()),
RuntimeContextType.SCALAR);
}

// Compile invocant in scalar context
Expand Down Expand Up @@ -546,7 +551,8 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
case "+", "-", "*", "/", "%", "**",
"&", "|", "^", "<<", ">>",
"binary&", "binary|", "binary^",
"&.", "|.", "^." -> true;
"&.", "|.", "^.",
"." -> true; // String concat always takes SCALAR operands
default -> false;
};
int outerCtx = bytecodeCompiler.currentCallContext;
Expand Down
Loading