Skip to content

Commit 4e59947

Browse files
authored
Merge pull request #44 from fglock/fglock/llm-work
Implement $^P debugger flags support for eval source line retention
2 parents df5075b + 04d32b5 commit 4e59947

File tree

3 files changed

+103
-6
lines changed

3 files changed

+103
-6
lines changed

src/main/java/org/perlonjava/runtime/GlobalContext.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public static void initializeGlobals(CompilerOptions compilerOptions) {
9494
// GlobalVariable.globalVariables.put(encodeSpecialVar("R"), new ScalarSpecialVariable(ScalarSpecialVariable.Id.LAST_REGEXP_CODE_RESULT)); // $^R
9595
GlobalVariable.getGlobalVariable(encodeSpecialVar("R")); // initialize $^R to "undef" - writable variable
9696
GlobalVariable.getGlobalVariable(encodeSpecialVar("A")).set(""); // initialize $^A to "" - format accumulator variable
97+
GlobalVariable.getGlobalVariable(encodeSpecialVar("P")).set(0); // initialize $^P to 0 - debugger flags
9798
GlobalVariable.globalVariables.put(encodeSpecialVar("LAST_SUCCESSFUL_PATTERN"), new ScalarSpecialVariable(ScalarSpecialVariable.Id.LAST_SUCCESSFUL_PATTERN));
9899
GlobalVariable.globalVariables.put(encodeSpecialVar("LAST_FH"), new ScalarSpecialVariable(ScalarSpecialVariable.Id.LAST_FH)); // $^LAST_FH
99100

src/main/java/org/perlonjava/runtime/RuntimeCode.java

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ protected boolean removeEldestEntry(Map.Entry<Class<?>, MethodHandle> eldest) {
5555
// Temporary storage for anonymous subroutines and eval string compiler context
5656
public static HashMap<String, Class<?>> anonSubs = new HashMap<>(); // temp storage for makeCodeObject()
5757
public static HashMap<String, EmitterContext> evalContext = new HashMap<>(); // storage for eval string compiler context
58+
// Runtime eval counter for generating unique filenames when $^P is set
59+
private static int runtimeEvalCounter = 1;
5860
// Method object representing the compiled subroutine
5961
public MethodHandle methodHandle;
6062
public boolean isStatic;
@@ -148,11 +150,32 @@ public static Class<?> evalStringHelper(RuntimeScalar code, String evalTag) thro
148150
evalCompilerOptions.isUnicodeSource = true;
149151
}
150152

153+
// Check $^P to determine if we should use caching
154+
// When debugging is enabled, we want each eval to get a unique filename
155+
int debugFlags = GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("P")).getInt();
156+
boolean isDebugging = debugFlags != 0;
157+
158+
// Override the filename with a runtime-generated eval number when debugging
159+
String actualFileName = evalCompilerOptions.fileName;
160+
if (isDebugging) {
161+
synchronized (RuntimeCode.class) {
162+
actualFileName = "(eval " + runtimeEvalCounter++ + ")";
163+
}
164+
}
165+
151166
// Check if the result is already cached (include hasUnicode in cache key)
167+
// Skip caching when $^P is set, so each eval gets a unique filename
152168
String cacheKey = code.toString() + '\0' + evalTag + '\0' + hasUnicode;
153-
synchronized (evalCache) {
154-
if (evalCache.containsKey(cacheKey)) {
155-
return evalCache.get(cacheKey);
169+
Class<?> cachedClass = null;
170+
if (!isDebugging) {
171+
synchronized (evalCache) {
172+
if (evalCache.containsKey(cacheKey)) {
173+
cachedClass = evalCache.get(cacheKey);
174+
}
175+
}
176+
177+
if (cachedClass != null) {
178+
return cachedClass;
156179
}
157180
}
158181

@@ -211,14 +234,77 @@ public static Class<?> evalStringHelper(RuntimeScalar code, String evalTag) thro
211234
);
212235
}
213236

214-
// Cache the result
215-
synchronized (evalCache) {
216-
evalCache.put(cacheKey, generatedClass);
237+
// Cache the result (unless debugging is enabled)
238+
if (!isDebugging) {
239+
synchronized (evalCache) {
240+
evalCache.put(cacheKey, generatedClass);
241+
}
217242
}
218243

244+
// Store source lines in symbol table if $^P flags are set
245+
storeSourceLines(evalString, actualFileName, ast);
246+
219247
return generatedClass;
220248
}
221249

250+
/**
251+
* Stores source lines in the symbol table for debugger support when $^P flags are set.
252+
*
253+
* @param evalString The source code string to store
254+
* @param filename The filename (e.g., "(eval 1)")
255+
* @param ast The AST to check for subroutine definitions
256+
*/
257+
private static void storeSourceLines(String evalString, String filename, Node ast) {
258+
// Check $^P for debugger flags
259+
int debugFlags = GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("P")).getInt();
260+
// 0x02 (2): Line-by-line debugging (also saves source like 0x400)
261+
// 0x400 (1024): Save source code lines
262+
// 0x800 (2048): Include evals that generate no subroutines
263+
// 0x1000 (4096): Include source that did not compile
264+
boolean shouldSaveSource = (debugFlags & 0x02) != 0 || (debugFlags & 0x400) != 0;
265+
boolean saveWithoutSubs = (debugFlags & 0x800) != 0;
266+
267+
if (shouldSaveSource) {
268+
// Note: We can't reliably detect subroutine definitions from the AST because
269+
// subroutines are processed at parse-time and removed from the AST.
270+
// Use a simple heuristic: check if the eval string contains "sub " followed by
271+
// an identifier or block.
272+
boolean definesSubs = evalString.matches("(?s).*\\bsub\\s+(?:\\w+|\\{).*");
273+
274+
// Only save if either:
275+
// - The eval defines subroutines, OR
276+
// - The 0x800 flag is set (save evals without subs)
277+
if (!definesSubs && !saveWithoutSubs) {
278+
return; // Skip this eval
279+
}
280+
// Store in the symbol table as @{"_<(eval N)"}
281+
String symbolKey = "_<" + filename;
282+
283+
// Split the eval string into lines (without including trailing empty strings)
284+
String[] lines = evalString.split("\n");
285+
286+
// Create the array with the format expected by the debugger:
287+
// [0] = undef, [1..n] = lines with \n, [n+1] = \n, [n+2] = ;
288+
String arrayKey = "main::" + symbolKey;
289+
RuntimeArray sourceArray = GlobalVariable.getGlobalArray(arrayKey);
290+
sourceArray.elements.clear();
291+
292+
// Index 0: undef
293+
sourceArray.elements.add(RuntimeScalarCache.scalarUndef);
294+
295+
// Indexes 1..n: each line with "\n" appended
296+
for (String line : lines) {
297+
sourceArray.elements.add(new RuntimeScalar(line + "\n"));
298+
}
299+
300+
// Index n+1: "\n"
301+
sourceArray.elements.add(new RuntimeScalar("\n"));
302+
303+
// Index n+2: ";"
304+
sourceArray.elements.add(new RuntimeScalar(";"));
305+
}
306+
}
307+
222308
// make sure we return a RuntimeScalar from __SUB__
223309
public static RuntimeScalar selfReferenceMaybeNull(RuntimeScalar codeRef) {
224310
return codeRef == null

src/main/java/org/perlonjava/runtime/RuntimeScalar.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,11 @@ public RuntimeArray arrayDeref() {
776776
yield AutovivificationArray.createAutovivifiedArray(this);
777777
}
778778
case ARRAYREFERENCE -> (RuntimeArray) value;
779+
case GLOB -> {
780+
// When dereferencing a typeglob as an array, return the array slot
781+
RuntimeGlob glob = (RuntimeGlob) value;
782+
yield GlobalVariable.getGlobalArray(glob.globName);
783+
}
779784
case STRING, BYTE_STRING ->
780785
throw new PerlCompilerException("Can't use string (\"" + this + "\") as an ARRAY ref while \"strict refs\" in use");
781786
case TIED_SCALAR -> tiedFetch().arrayDeref();
@@ -836,6 +841,11 @@ public RuntimeHash hashDeref() {
836841
case HASHREFERENCE ->
837842
// Simple case: already a hash reference, just return the hash
838843
(RuntimeHash) value;
844+
case GLOB -> {
845+
// When dereferencing a typeglob as a hash, return the hash slot
846+
RuntimeGlob glob = (RuntimeGlob) value;
847+
yield GlobalVariable.getGlobalHash(glob.globName);
848+
}
839849
case STRING, BYTE_STRING ->
840850
// Strict refs violation: attempting to use a string as a hash ref
841851
throw new PerlCompilerException("Can't use string (\"" + this + "\") as a HASH ref while \"strict refs\" in use");

0 commit comments

Comments
 (0)