Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ priorities and future plans.

- Extend CPAN release acceptance coverage to Excel::Writer::XLSX with a timeout suitable for its large test suite.

- Complete `tr///` compatibility for extended Unicode and surrogate scalars,
identity lvalues, and `chop`/`chomp` diagnostics on both execution backends.

- Restore `local` compatibility for tied hash and array elements, sparse
arrays, magic stashes, implicit `$_` foreach aliases (including early
return), and localized regex captures on both execution backends.
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/perlonjava/frontend/parser/OperatorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1461,9 +1461,27 @@ static OperatorNode parseChompChop(Parser parser, LexerToken token, int currentI
// stop before any "," at the same precedence level.
operand = ListNode.makeList(parser.parseExpression(parser.getPrecedence(",") + 1));
}
if (containsTransliteration(operand)) {
parser.throwError("Can't modify transliteration (tr///) in " + token.text);
}
return new OperatorNode(token.text, operand, currentIndex);
}

private static boolean containsTransliteration(Node node) {
if (node instanceof OperatorNode operator) {
if (operator.operator.equals("tr") || operator.operator.equals("transliterate")) return true;
return false;
}
if (node instanceof BinaryOperatorNode binary) {
return (binary.operator.equals("=~") || binary.operator.equals("!~"))
&& containsTransliteration(binary.right);
}
if (node instanceof ListNode list) {
for (Node element : list.elements) if (containsTransliteration(element)) return true;
}
return false;
}

static OperatorNode parseDieWarn(Parser parser, LexerToken token, int currentIndex) {
int dieKeywordIndex = currentIndex; // Capture token position BEFORE parsing args
ListNode operand = ListParser.parseZeroOrMoreList(parser, 0, false, true, false, false);
Expand Down
23 changes: 22 additions & 1 deletion src/main/java/org/perlonjava/frontend/parser/ParseInfix.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import org.perlonjava.app.cli.CompilerOptions;

import org.perlonjava.frontend.analysis.ConstantFoldingVisitor;
import org.perlonjava.frontend.analysis.RegexLiteralAnalyzer;
import org.perlonjava.frontend.astnode.*;
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.operators.RuntimeTransliterate;
import org.perlonjava.runtime.perlmodule.Strict;
import org.perlonjava.runtime.runtimetypes.GlobalVariable;
import org.perlonjava.runtime.runtimetypes.ErrorMessageUtil;
Expand Down Expand Up @@ -661,6 +663,21 @@ private static void rejectAggregateRegexMutation(Parser parser, Node left,
|| regexOperator.operator.equals("transliterate"))) {
return;
}
if (!regexOperator.operator.equals("replaceRegex")
&& regexOperator.operand instanceof ListNode args
&& args.elements.size() >= 3) {
String search = RegexLiteralAnalyzer.constantString(args.elements.get(0));
String replacement = RegexLiteralAnalyzer.constantString(args.elements.get(1));
String flags = RegexLiteralAnalyzer.constantString(args.elements.get(2));
if (search != null && replacement != null && flags != null) {
RuntimeTransliterate operation = new RuntimeTransliterate();
operation.compileTransliteration(search, replacement, flags);
if (!operation.modifiesTarget()) return;
if (left instanceof StringNode || left instanceof NumberNode) {
parser.throwError("Can't modify constant item in transliteration (tr///)");
}
}
}
if (!(left instanceof OperatorNode aggregate)
|| !(aggregate.operator.equals("@") || aggregate.operator.equals("%"))
|| !(aggregate.operand instanceof IdentifierNode identifier)) {
Expand All @@ -671,7 +688,11 @@ private static void rejectAggregateRegexMutation(Parser parser, Node left,
var entry = parser.ctx.symbolTable.getSymbolEntry(
aggregate.operator + identifier.name);
boolean lexical = entry != null
&& ("my".equals(entry.decl()) || "state".equals(entry.decl()));
&& ("my".equals(entry.decl()) || "state".equals(entry.decl())
// eval STRING exposes captured lexicals through internal
// package aliases, without changing their Perl identity.
|| (entry.perlPackage() != null
&& entry.perlPackage().startsWith("PerlOnJava::_BEGIN_")));
parser.throwError("Can't modify " + (lexical ? "private " : "")
+ kind + (lexical ? "" : " dereference")
+ " in " + (regexOperator.operator.equals("replaceRegex")
Expand Down
Loading
Loading