-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSpecialBlockParser.java
More file actions
335 lines (300 loc) · 15.5 KB
/
SpecialBlockParser.java
File metadata and controls
335 lines (300 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package org.perlonjava.frontend.parser;
import org.perlonjava.app.cli.CompilerOptions;
import org.perlonjava.app.scriptengine.PerlLanguageProvider;
import org.perlonjava.backend.jvm.EmitterMethodCreator;
import org.perlonjava.frontend.astnode.*;
import org.perlonjava.frontend.lexer.LexerTokenType;
import org.perlonjava.frontend.semantic.ScopedSymbolTable;
import org.perlonjava.frontend.semantic.SymbolTable;
import org.perlonjava.runtime.runtimetypes.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.perlonjava.runtime.runtimetypes.GlobalContext.GLOBAL_PHASE;
import static org.perlonjava.runtime.runtimetypes.SpecialBlock.*;
/**
* The SpecialBlockParser class is responsible for parsing and executing special blocks
* in Perl scripts, such as BEGIN, END, INIT, CHECK, and UNITCHECK blocks.
*/
public class SpecialBlockParser {
private static ScopedSymbolTable symbolTable = new ScopedSymbolTable();
public static ScopedSymbolTable getCurrentScope() {
return symbolTable;
}
public static void setCurrentScope(ScopedSymbolTable st) {
symbolTable = st;
}
/**
* Parses a special block.
*
* @param parser The parser instance to use for parsing.
* @return A Node representing "undef".
*/
static Node parseSpecialBlock(Parser parser) {
// Consume the block name token
String blockName = TokenUtils.consume(parser).text;
// ADJUST blocks are only allowed inside class blocks
if ("ADJUST".equals(blockName) && !parser.isInClassBlock) {
throw new PerlCompilerException(parser.tokenIndex,
"ADJUST blocks are only allowed inside class blocks", parser.ctx.errorUtil);
}
// Consume the opening brace '{'
TokenUtils.consume(parser, LexerTokenType.OPERATOR, "{");
// ADJUST blocks have implicit $self, so set isInMethod flag
boolean wasInMethod = parser.isInMethod;
if ("ADJUST".equals(blockName) && parser.isInClassBlock) {
parser.isInMethod = true;
}
// Parse the block content
BlockNode block = ParseBlock.parseBlock(parser);
// Restore the isInMethod flag
parser.isInMethod = wasInMethod;
// Consume the closing brace '}'
TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}");
// Before executing BEGIN blocks, process any pending heredocs.
// This handles cases like: BEGIN { eval <<'END' } ... \n heredoc content \n END
// The heredoc content comes after the newline, but BEGIN must execute immediately.
// We need to fill in the heredoc content before BEGIN tries to use it.
if ("BEGIN".equals(blockName) && !parser.getHeredocNodes().isEmpty()) {
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("HEREDOC_BEGIN_FIX: Found " + parser.getHeredocNodes().size() + " pending heredocs after BEGIN block");
int savedIndex = parser.tokenIndex;
// Find the next NEWLINE token
int newlineIndex = -1;
for (int i = savedIndex; i < parser.tokens.size(); i++) {
if (parser.tokens.get(i).type == LexerTokenType.NEWLINE) {
newlineIndex = i;
break;
}
}
if (newlineIndex >= 0) {
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("HEREDOC_BEGIN_FIX: Processing at newlineIndex=" + newlineIndex + ", will skip to after heredocs");
// Temporarily advance to the newline to process heredocs
parser.tokenIndex = newlineIndex;
ParseHeredoc.parseHeredocAfterNewline(parser);
// Save where to skip to when we later encounter this specific newline
parser.heredocSkipToIndex = parser.tokenIndex;
parser.heredocNewlineIndex = newlineIndex;
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("HEREDOC_BEGIN_FIX: Set heredocSkipToIndex=" + parser.heredocSkipToIndex + ", heredocNewlineIndex=" + newlineIndex);
// Restore tokenIndex to continue parsing from after the '}'
parser.tokenIndex = savedIndex;
}
}
// ADJUST blocks in class context are not executed at parse time
// They are compiled as anonymous subs and stored for the constructor
if ("ADJUST".equals(blockName) && parser.isInClassBlock) {
// Create an anonymous sub that captures lexical variables
SubroutineNode adjustSub = new SubroutineNode(
null, // anonymous
null, // no prototype
null, // no attributes
block,
false,
parser.tokenIndex);
// Store in parser's ADJUST blocks list
parser.classAdjustBlocks.add(adjustSub);
// Return the anonymous sub node (won't be executed now)
return adjustSub;
}
// Execute other special blocks normally
runSpecialBlock(parser, blockName, block);
// Return an undefined operator node marked as compile-time-only
// so it doesn't affect the file's return value
OperatorNode result = new OperatorNode("undef", null, parser.tokenIndex);
result.setAnnotation("compileTimeOnly", true);
return result;
}
/**
* Executes a special block with the given block phase and block AST.
* Uses VOID context by default.
*
* @param parser The parser instance.
* @param blockPhase The phase of the block (e.g., BEGIN, END).
* @param block The block AST to execute.
* @return A RuntimeList containing the result of the execution.
*/
static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block) {
return runSpecialBlock(parser, blockPhase, block, RuntimeContextType.VOID);
}
/**
* Executes a special block with the given block phase, block AST, and context.
*
* @param parser The parser instance.
* @param blockPhase The phase of the block (e.g., BEGIN, END).
* @param block The block AST to execute.
* @param contextType The context to use for execution (VOID, SCALAR, LIST).
* @return A RuntimeList containing the result of the execution.
*/
static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block, int contextType) {
int tokenIndex = parser.tokenIndex;
// Create AST nodes for setting up the capture variables and package declaration
List<Node> nodes = new ArrayList<>();
if (block instanceof BlockNode blockNode) {
// Emit as first operation inside the block: local ${^GLOBAL_PHASE} = "BEGIN"
String phaseName = blockPhase.equals("BEGIN") || blockPhase.equals("UNITCHECK")
? "START"
: blockPhase;
blockNode.elements.addFirst(
new BinaryOperatorNode("=",
new OperatorNode("local",
new OperatorNode("$",
new IdentifierNode(GLOBAL_PHASE, tokenIndex),
tokenIndex),
tokenIndex),
new StringNode(phaseName, tokenIndex),
tokenIndex));
}
// Declare capture variables
Map<Integer, SymbolTable.SymbolEntry> outerVars = parser.ctx.symbolTable.getAllVisibleVariables();
for (SymbolTable.SymbolEntry entry : outerVars.values()) {
if (!entry.name().equals("@_") && !entry.decl().isEmpty()) {
// Skip lexical subs (entries starting with &) - they are stored as hidden variables
// and don't need to be captured in BEGIN blocks
if (entry.name().startsWith("&")) {
continue;
}
String packageName;
boolean isFromOuterScope = false;
if (entry.decl().equals("our")) {
// "our" variable lives in a Perl package
packageName = entry.perlPackage();
// Emit: package PKG
nodes.add(
new OperatorNode("package",
new IdentifierNode(packageName, tokenIndex), tokenIndex));
} else {
OperatorNode ast = entry.ast();
isFromOuterScope = RuntimeCode.evalBeginIds.containsKey(ast);
int beginId = RuntimeCode.evalBeginIds.computeIfAbsent(
ast,
k -> EmitterMethodCreator.classCounter++);
packageName = PersistentVariable.beginPackage(beginId);
// Emit: package BEGIN_PKG
nodes.add(
new OperatorNode("package",
new IdentifierNode(packageName, tokenIndex), tokenIndex));
}
// CLEAN FIX: For eval STRING, make special globals aliases to closed variables
// This allows BEGIN blocks to access outer lexical variables with their runtime values.
//
// In perl5: my @arr = qw(a b); eval q{ BEGIN { say @arr } }; # prints: a b
// The special global BEGIN_PKG::@arr is an ALIAS to the closed @arr variable.
//
// Implementation: Set the global variable to reference the same runtime object.
// Only alias if the variable is from the outer (eval) scope, NOT if it's a newly
// declared variable in the current compilation unit that just happens to share
// the same name.
if (!entry.decl().equals("our") && isFromOuterScope) {
RuntimeCode.EvalRuntimeContext evalCtx = RuntimeCode.getEvalRuntimeContext();
if (evalCtx != null) {
Object runtimeValue = evalCtx.getRuntimeValue(entry.name());
if (runtimeValue != null) {
// Create alias: set special global to reference the runtime object
// IMPORTANT: Global variable keys do NOT include the sigil
// entry.name() is "@arr" but the key should be "packageName::arr"
String varNameWithoutSigil = entry.name().substring(1); // Remove the sigil
String fullName = packageName + "::" + varNameWithoutSigil;
// Put in the appropriate global map based on variable type
if (runtimeValue instanceof RuntimeArray) {
GlobalVariable.globalArrays.put(fullName, (RuntimeArray) runtimeValue);
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("BEGIN block: Aliased array " + fullName);
} else if (runtimeValue instanceof RuntimeHash) {
GlobalVariable.globalHashes.put(fullName, (RuntimeHash) runtimeValue);
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("BEGIN block: Aliased hash " + fullName);
} else if (runtimeValue instanceof RuntimeScalar) {
GlobalVariable.globalVariables.put(fullName, (RuntimeScalar) runtimeValue);
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("BEGIN block: Aliased scalar " + fullName);
}
}
}
}
// Emit: our $var
// When we've aliased the variable above, the "our" declaration will fetch the
// existing global (our alias) instead of creating a new empty one.
nodes.add(
new OperatorNode(
"our",
new OperatorNode(
entry.name().substring(0, 1),
new IdentifierNode(entry.name().substring(1), tokenIndex),
tokenIndex),
tokenIndex));
}
}
// Emit: package PKG
nodes.add(
new OperatorNode("package",
new IdentifierNode(
parser.ctx.symbolTable.getCurrentPackage(), tokenIndex), tokenIndex));
SubroutineNode anonSub =
new SubroutineNode(
null,
null,
null,
block,
false,
tokenIndex);
if (blockPhase.equals("BEGIN")) {
// BEGIN - execute immediately
nodes.add(
new BinaryOperatorNode(
"->",
anonSub,
new ListNode(tokenIndex),
tokenIndex
)
);
} else {
// Not BEGIN - return a sub to execute later
nodes.add(anonSub);
}
CompilerOptions parsedArgs = parser.ctx.compilerOptions.clone();
parsedArgs.compileOnly = false; // Special blocks are always run
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("Special block captures " + parser.ctx.symbolTable.getAllVisibleVariables());
RuntimeList result;
try {
setCurrentScope(parser.ctx.symbolTable);
// Mark all wrapper nodes to skip DEBUG opcodes
// These are infrastructure nodes, not user-debuggable code
for (Node n : nodes) {
if (n instanceof AbstractNode an) {
an.setAnnotation("skipDebug", true);
}
}
result = PerlLanguageProvider.executePerlAST(
new BlockNode(nodes, tokenIndex),
parser.tokens,
parsedArgs,
contextType);
} catch (PerlExitException e) {
// exit() inside BEGIN block should terminate the program, not cause compilation error
// Re-throw so it propagates to the CLI (Main.main()) which will call System.exit()
throw e;
} catch (Throwable t) {
if (parsedArgs.debugEnabled) {
// Print full JVM stack
t.printStackTrace();
System.out.println();
}
String message = t.getMessage();
if (message == null) {
message = t.getClass().getSimpleName() + " during " + blockPhase;
}
if (!message.endsWith("\n")) {
message += "\n";
}
message += blockPhase + " failed--compilation aborted";
throw new PerlCompilerException(parser.tokenIndex, message, parser.ctx.errorUtil);
}
GlobalVariable.getGlobalVariable("main::@").set(""); // Reset error variable
if (!blockPhase.equals("BEGIN")) {
RuntimeScalar codeRef = result.getFirst();
switch (blockPhase) {
case "END" -> saveEndBlock(codeRef);
case "INIT" -> saveInitBlock(codeRef);
case "CHECK" -> saveCheckBlock(codeRef);
case "UNITCHECK" -> RuntimeArray.push(parser.ctx.unitcheckBlocks, codeRef);
}
}
return result;
}
}