diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java index c484a205b..feca84082 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java @@ -11,6 +11,7 @@ import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.perlmodule.Strict; import org.perlonjava.runtime.perlmodule.Warnings; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.*; import java.util.ArrayList; @@ -767,6 +768,18 @@ static void handleAssignOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo if (!ctx.symbolTable.isFeatureCategoryEnabled("refaliasing")) { throw new PerlCompilerException(node.tokenIndex, "Experimental aliasing via reference not enabled", ctx.errorUtil); } + // Emit experimental warning if warnings are enabled + if (ctx.symbolTable.isWarningCategoryEnabled("experimental::refaliasing")) { + try { + WarnDie.warn( + new RuntimeScalar("Aliasing via reference is experimental"), + new RuntimeScalar(ctx.errorUtil.warningLocation(node.tokenIndex)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("Aliasing via reference is experimental" + ctx.errorUtil.warningLocation(node.tokenIndex) + "."); + } + } // TODO: Implement proper reference aliasing // For now, we just assign the reference value without creating an alias // This is not fully correct but allows tests to progress @@ -1096,11 +1109,12 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { if (CompilerOptions.DEBUG_ENABLED) emitterVisitor.ctx.logDebug("MY " + operator + " " + sigil + name); if (emitterVisitor.ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) { if (Warnings.warningManager.isWarningEnabled("redefine")) { + String message = operator.equals("our") + ? "\"" + operator + "\" variable " + var + " redeclared" + : "\"" + operator + "\" variable " + var + " masks earlier declaration in same scope"; System.err.println( emitterVisitor.ctx.errorUtil.errorMessage(node.getIndex(), - "Warning: \"" + operator + "\" variable " - + var - + " masks earlier declaration in same ctx.symbolTable")); + message)); } } int varIndex = emitterVisitor.ctx.symbolTable.addVariable(var, operator, sigilNode); diff --git a/src/main/java/org/perlonjava/frontend/parser/FieldParser.java b/src/main/java/org/perlonjava/frontend/parser/FieldParser.java index 11a998bba..441b1f830 100644 --- a/src/main/java/org/perlonjava/frontend/parser/FieldParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/FieldParser.java @@ -5,7 +5,9 @@ import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; +import org.perlonjava.runtime.runtimetypes.RuntimeScalar; /** * FieldParser handles parsing of field declarations in Perl classes. @@ -29,9 +31,23 @@ public class FieldParser { * @return A comment node placeholder for the field */ public static Node parseFieldDeclaration(Parser parser) { + int index = parser.tokenIndex; // Consume 'field' keyword TokenUtils.consume(parser, LexerTokenType.IDENTIFIER, "field"); + // Emit experimental warning for 'field' if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::class")) { + try { + WarnDie.warn( + new RuntimeScalar("field is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(index)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("field is experimental" + parser.ctx.errorUtil.warningLocation(index) + "."); + } + } + // Parse the field variable (sigil + name) LexerToken token = TokenUtils.peek(parser); diff --git a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java index ce48e6ec6..d3d34c7d9 100644 --- a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java @@ -234,11 +234,11 @@ private static void addVariableToScope(EmitterContext ctx, String operator, Oper String name = ((IdentifierNode) identifierNode).name; String var = sigil + name; if (ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) { + String message = operator.equals("our") + ? "\"" + operator + "\" variable " + var + " redeclared" + : "\"" + operator + "\" variable " + var + " masks earlier declaration in same scope"; System.err.println( - ctx.errorUtil.errorMessage(node.getIndex(), - "Warning: \"" + operator + "\" variable " - + var - + " masks earlier declaration in same ctx.symbolTable")); + ctx.errorUtil.errorMessage(node.getIndex(), message)); } int varIndex = ctx.symbolTable.addVariable(var, operator, node); // Note: the isDeclaredReference flag is stored in node.annotations @@ -284,11 +284,11 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(currentIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(currentIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(currentIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(currentIndex) + "."); } } @@ -328,11 +328,11 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex) + "."); } } @@ -862,11 +862,11 @@ static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(currentIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(currentIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(currentIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(currentIndex) + "."); } } @@ -905,11 +905,11 @@ static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex) + "."); } } diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java index 3fab0526e..deed326f3 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java @@ -10,6 +10,7 @@ import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.runtime.mro.InheritanceResolver; import org.perlonjava.runtime.operators.ModuleOperators; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.operators.VersionHelper; import org.perlonjava.runtime.perlmodule.Universal; import org.perlonjava.runtime.runtimetypes.*; @@ -351,6 +352,19 @@ public static Node parseDeferStatement(Parser parser) { int index = parser.tokenIndex; TokenUtils.consume(parser, LexerTokenType.IDENTIFIER); // "defer" + // Emit experimental warning if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::defer")) { + try { + WarnDie.warn( + new RuntimeScalar("defer is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(index)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("defer is experimental" + parser.ctx.errorUtil.warningLocation(index) + "."); + } + } + // Parse the defer block TokenUtils.consume(parser, LexerTokenType.OPERATOR, "{"); Node deferBlock = ParseBlock.parseBlock(parser); @@ -679,6 +693,20 @@ public static Node parsePackageDeclaration(Parser parser, LexerToken token) { packageExistsCache.put(packageName, true); boolean isClass = token.text.equals("class"); + + // Emit experimental warning for 'class' if warnings are enabled + if (isClass && parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::class")) { + try { + WarnDie.warn( + new RuntimeScalar("class is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(parser.tokenIndex)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("class is experimental" + parser.ctx.errorUtil.warningLocation(parser.tokenIndex) + "."); + } + } + IdentifierNode nameNode = new IdentifierNode(packageName, parser.tokenIndex); OperatorNode packageNode = new OperatorNode(token.text, nameNode, parser.tokenIndex); packageNode.setAnnotation("isClass", isClass); diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java index b70b1488c..e9ccb39de 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java @@ -8,8 +8,10 @@ import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.frontend.semantic.SymbolTable; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.NameNormalizer; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; +import org.perlonjava.runtime.runtimetypes.RuntimeScalar; import java.util.ArrayList; import java.util.List; @@ -121,6 +123,19 @@ public static Node parseStatement(Parser parser, String label) { case "method" -> { if (parser.ctx.symbolTable.isFeatureCategoryEnabled("class")) { + // Emit experimental warning for 'method' if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::class")) { + try { + WarnDie.warn( + new RuntimeScalar("method is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(currentIndex)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("method is experimental" + parser.ctx.errorUtil.warningLocation(currentIndex) + "."); + } + } + // Parse method more directly parser.tokenIndex++; // consume "method" diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index 77efd771d..aaed6e781 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java @@ -209,6 +209,18 @@ public String errorMessage(int index, String message) { return message + " at " + loc.fileName() + " line " + loc.lineNumber() + ", near " + errorMessageQuote(nearString) + "\n"; } + /** + * Gets a simple warning location string in the format " at filename line N". + * This matches system Perl's warning format without the ", near" suffix. + * + * @param index the index of the token where the warning occurred + * @return the location string (e.g., " at script.pl line 42") + */ + public String warningLocation(int index) { + SourceLocation loc = getSourceLocationAccurate(index); + return " at " + loc.fileName() + " line " + loc.lineNumber(); + } + private String buildNearString(int index) { int end = Math.min(tokens.size() - 1, index + 5); StringBuilder sb = new StringBuilder();