-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathSpecialBlockParser.java
More file actions
274 lines (241 loc) · 11.8 KB
/
SpecialBlockParser.java
File metadata and controls
274 lines (241 loc) · 11.8 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
package org.perlonjava.parser;
import org.perlonjava.CompilerOptions;
import org.perlonjava.astnode.*;
import org.perlonjava.codegen.EmitterMethodCreator;
import org.perlonjava.lexer.LexerTokenType;
import org.perlonjava.runtime.*;
import org.perlonjava.scriptengine.PerlLanguageProvider;
import org.perlonjava.symbols.ScopedSymbolTable;
import org.perlonjava.symbols.SymbolTable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.perlonjava.runtime.GlobalContext.GLOBAL_PHASE;
import static org.perlonjava.runtime.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, "}");
// 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
return new OperatorNode("undef", null, parser.tokenIndex);
}
/**
* Executes a special block with the given block phase and block AST.
*
* @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) {
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;
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 {
// "my" or "state" variable live in a special BEGIN package
// Retrieve the variable id from the AST; create a new id if needed
OperatorNode ast = entry.ast();
if (ast.id == 0) {
ast.id = EmitterMethodCreator.classCounter++;
}
packageName = PersistentVariable.beginPackage(ast.id);
// 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.
if (!entry.decl().equals("our")) {
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);
parser.ctx.logDebug("BEGIN block: Aliased array " + fullName);
} else if (runtimeValue instanceof RuntimeHash) {
GlobalVariable.globalHashes.put(fullName, (RuntimeHash) runtimeValue);
parser.ctx.logDebug("BEGIN block: Aliased hash " + fullName);
} else if (runtimeValue instanceof RuntimeScalar) {
GlobalVariable.globalVariables.put(fullName, (RuntimeScalar) runtimeValue);
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
parser.ctx.logDebug("Special block captures " + parser.ctx.symbolTable.getAllVisibleVariables());
RuntimeList result;
try {
setCurrentScope(parser.ctx.symbolTable);
result = PerlLanguageProvider.executePerlAST(
new BlockNode(nodes, tokenIndex),
parser.tokens,
parsedArgs);
} 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;
}
}