Skip to content

Commit 055561a

Browse files
authored
Merge pull request #56 from fglock/fix-bop-parser
Fix autoquoting of and/or/xor keywords before =>
2 parents 4fe41c4 + f74b54f commit 055561a

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed

src/main/java/org/perlonjava/parser/ListParser.java

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ static ListNode parseZeroOrOneList(Parser parser, int minItems) {
5151
if (expr.elements.size() > 1) {
5252
parser.throwError("syntax error");
5353
}
54-
} else if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text) || token.text.equals(",")) {
54+
} else if (token.type == LexerTokenType.EOF || isListTerminator(parser, token) || token.text.equals(",")) {
5555
// No argument
5656
expr = new ListNode(parser.tokenIndex);
5757
} else {
@@ -105,7 +105,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo
105105
// Check for infix operators after the regex (like . for concatenation)
106106
while (true) {
107107
token = TokenUtils.peek(parser);
108-
if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text)) {
108+
if (token.type == LexerTokenType.EOF || isListTerminator(parser, token)) {
109109
break;
110110
}
111111
int tokenPrecedence = parser.getPrecedence(token.text);
@@ -122,7 +122,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo
122122

123123
expr.elements.add(left);
124124
token = TokenUtils.peek(parser);
125-
if (token.type != LexerTokenType.EOF && !ParserTables.LIST_TERMINATORS.contains(token.text)) {
125+
if (token.type != LexerTokenType.EOF && !isListTerminator(parser, token)) {
126126
// Consume comma
127127
PrototypeArgs.consumeCommaIfPresent(parser, false);
128128
}
@@ -175,7 +175,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo
175175
TokenUtils.consume(parser);
176176
expr.elements.addAll(parseList(parser, ")", 0));
177177
} else {
178-
while (token.type != LexerTokenType.EOF && !ParserTables.LIST_TERMINATORS.contains(token.text)) {
178+
while (token.type != LexerTokenType.EOF && !isListTerminator(parser, token)) {
179179
// Argument without parentheses
180180
expr.elements.add(parser.parseExpression(parser.getPrecedence(",")));
181181
token = TokenUtils.peek(parser);
@@ -212,6 +212,31 @@ static LexerToken consumeCommas(Parser parser) {
212212
return token;
213213
}
214214

215+
/**
216+
* Checks if a token is a list terminator, with special handling for autoquoting.
217+
* Keywords like "and", "or", "xor" should not terminate a list if followed by "=>",
218+
* as they should be treated as hash keys in that context.
219+
*/
220+
static boolean isListTerminator(Parser parser, LexerToken token) {
221+
if (!ParserTables.LIST_TERMINATORS.contains(token.text)) {
222+
return false;
223+
}
224+
225+
// Special case: and/or/xor before => should be treated as barewords, not terminators
226+
if (token.text.equals("and") || token.text.equals("or") || token.text.equals("xor")) {
227+
// Look ahead to see if => follows
228+
int saveIndex = parser.tokenIndex;
229+
TokenUtils.consume(parser); // consume and/or/xor
230+
LexerToken nextToken = TokenUtils.peek(parser);
231+
parser.tokenIndex = saveIndex; // restore
232+
if (nextToken.text.equals("=>")) {
233+
return false; // Not a terminator, it's a hash key
234+
}
235+
}
236+
237+
return true;
238+
}
239+
215240
/**
216241
* Parses a generic list with a specified closing delimiter. This method is
217242
* used for parsing various constructs like parentheses, hash literals, and
@@ -269,8 +294,22 @@ public static boolean looksLikeEmptyList(Parser parser) {
269294
LexerToken token1 = parser.tokens.get(parser.tokenIndex); // Next token including spaces
270295
LexerToken nextToken = TokenUtils.peek(parser); // After spaces
271296

272-
if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text)
273-
|| token.text.equals("->")) {
297+
// Check if this is a list terminator, but we need to restore position for the check
298+
boolean isTerminator = false;
299+
if (ParserTables.LIST_TERMINATORS.contains(token.text)) {
300+
// Special case: check if and/or/xor followed by =>
301+
if (token.text.equals("and") || token.text.equals("or") || token.text.equals("xor")) {
302+
if (nextToken.text.equals("=>")) {
303+
isTerminator = false; // Not a terminator, it's a hash key
304+
} else {
305+
isTerminator = true;
306+
}
307+
} else {
308+
isTerminator = true;
309+
}
310+
}
311+
312+
if (token.type == LexerTokenType.EOF || isTerminator || token.text.equals("->")) {
274313
isEmptyList = true;
275314
} else if (token.text.equals("-")) {
276315
// -d, -e, -f, -l, -p, -x

src/main/java/org/perlonjava/parser/ParseInfix.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static Node parseInfixOperation(Parser parser, Node left, int precedence)
9595
left = new StringNode(((IdentifierNode) left).name, ((IdentifierNode) left).tokenIndex);
9696
}
9797
token = peek(parser);
98-
if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text) || token.text.equals(",") || token.text.equals("=>")) {
98+
if (token.type == LexerTokenType.EOF || ListParser.isListTerminator(parser, token) || token.text.equals(",") || token.text.equals("=>")) {
9999
// "postfix" comma
100100
return ListNode.makeList(left);
101101
}

src/main/java/org/perlonjava/parser/ParsePrimary.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,16 @@ static boolean isIsQuoteLikeOperator(String operator) {
214214
* @throws PerlCompilerException if the operator is not recognized or used incorrectly
215215
*/
216216
static Node parseOperator(Parser parser, LexerToken token, String operator) {
217+
// Check for autoquoting: keyword operators before => should be treated as barewords
218+
// This handles cases like: and => {...}, or => {...}, xor => {...}
219+
if (operator.equals("and") || operator.equals("or") || operator.equals("xor")) {
220+
String peekTokenText = peek(parser).text;
221+
if (peekTokenText.equals("=>")) {
222+
// Autoquote: convert operator keyword to string literal
223+
return new StringNode(token.text, parser.tokenIndex);
224+
}
225+
}
226+
217227
Node operand = null;
218228
switch (token.text) {
219229
case "(":

src/main/java/org/perlonjava/parser/PrototypeArgs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private static boolean allowsZeroArguments(String prototype) {
8787
private static boolean isArgumentTerminator(Parser parser) {
8888
var next = TokenUtils.peek(parser);
8989
return next.type == LexerTokenType.EOF ||
90-
ParserTables.LIST_TERMINATORS.contains(next.text) ||
90+
ListParser.isListTerminator(parser, next) ||
9191
Parser.isExpressionTerminator(next) ||
9292
// Assignment operators should terminate argument parsing
9393
next.text.equals("=") ||

0 commit comments

Comments
 (0)