diff --git a/src/main/java/org/perlonjava/parser/ListParser.java b/src/main/java/org/perlonjava/parser/ListParser.java index 23f09dbe7..334e72065 100644 --- a/src/main/java/org/perlonjava/parser/ListParser.java +++ b/src/main/java/org/perlonjava/parser/ListParser.java @@ -51,7 +51,7 @@ static ListNode parseZeroOrOneList(Parser parser, int minItems) { if (expr.elements.size() > 1) { parser.throwError("syntax error"); } - } else if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text) || token.text.equals(",")) { + } else if (token.type == LexerTokenType.EOF || isListTerminator(parser, token) || token.text.equals(",")) { // No argument expr = new ListNode(parser.tokenIndex); } else { @@ -105,7 +105,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo // Check for infix operators after the regex (like . for concatenation) while (true) { token = TokenUtils.peek(parser); - if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text)) { + if (token.type == LexerTokenType.EOF || isListTerminator(parser, token)) { break; } int tokenPrecedence = parser.getPrecedence(token.text); @@ -122,7 +122,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo expr.elements.add(left); token = TokenUtils.peek(parser); - if (token.type != LexerTokenType.EOF && !ParserTables.LIST_TERMINATORS.contains(token.text)) { + if (token.type != LexerTokenType.EOF && !isListTerminator(parser, token)) { // Consume comma PrototypeArgs.consumeCommaIfPresent(parser, false); } @@ -175,7 +175,7 @@ static ListNode parseZeroOrMoreList(Parser parser, int minItems, boolean wantBlo TokenUtils.consume(parser); expr.elements.addAll(parseList(parser, ")", 0)); } else { - while (token.type != LexerTokenType.EOF && !ParserTables.LIST_TERMINATORS.contains(token.text)) { + while (token.type != LexerTokenType.EOF && !isListTerminator(parser, token)) { // Argument without parentheses expr.elements.add(parser.parseExpression(parser.getPrecedence(","))); token = TokenUtils.peek(parser); @@ -212,6 +212,31 @@ static LexerToken consumeCommas(Parser parser) { return token; } + /** + * Checks if a token is a list terminator, with special handling for autoquoting. + * Keywords like "and", "or", "xor" should not terminate a list if followed by "=>", + * as they should be treated as hash keys in that context. + */ + static boolean isListTerminator(Parser parser, LexerToken token) { + if (!ParserTables.LIST_TERMINATORS.contains(token.text)) { + return false; + } + + // Special case: and/or/xor before => should be treated as barewords, not terminators + if (token.text.equals("and") || token.text.equals("or") || token.text.equals("xor")) { + // Look ahead to see if => follows + int saveIndex = parser.tokenIndex; + TokenUtils.consume(parser); // consume and/or/xor + LexerToken nextToken = TokenUtils.peek(parser); + parser.tokenIndex = saveIndex; // restore + if (nextToken.text.equals("=>")) { + return false; // Not a terminator, it's a hash key + } + } + + return true; + } + /** * Parses a generic list with a specified closing delimiter. This method is * used for parsing various constructs like parentheses, hash literals, and @@ -269,8 +294,22 @@ public static boolean looksLikeEmptyList(Parser parser) { LexerToken token1 = parser.tokens.get(parser.tokenIndex); // Next token including spaces LexerToken nextToken = TokenUtils.peek(parser); // After spaces - if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text) - || token.text.equals("->")) { + // Check if this is a list terminator, but we need to restore position for the check + boolean isTerminator = false; + if (ParserTables.LIST_TERMINATORS.contains(token.text)) { + // Special case: check if and/or/xor followed by => + if (token.text.equals("and") || token.text.equals("or") || token.text.equals("xor")) { + if (nextToken.text.equals("=>")) { + isTerminator = false; // Not a terminator, it's a hash key + } else { + isTerminator = true; + } + } else { + isTerminator = true; + } + } + + if (token.type == LexerTokenType.EOF || isTerminator || token.text.equals("->")) { isEmptyList = true; } else if (token.text.equals("-")) { // -d, -e, -f, -l, -p, -x diff --git a/src/main/java/org/perlonjava/parser/ParseInfix.java b/src/main/java/org/perlonjava/parser/ParseInfix.java index 46c7b6318..39fe5d627 100644 --- a/src/main/java/org/perlonjava/parser/ParseInfix.java +++ b/src/main/java/org/perlonjava/parser/ParseInfix.java @@ -95,7 +95,7 @@ public static Node parseInfixOperation(Parser parser, Node left, int precedence) left = new StringNode(((IdentifierNode) left).name, ((IdentifierNode) left).tokenIndex); } token = peek(parser); - if (token.type == LexerTokenType.EOF || ParserTables.LIST_TERMINATORS.contains(token.text) || token.text.equals(",") || token.text.equals("=>")) { + if (token.type == LexerTokenType.EOF || ListParser.isListTerminator(parser, token) || token.text.equals(",") || token.text.equals("=>")) { // "postfix" comma return ListNode.makeList(left); } diff --git a/src/main/java/org/perlonjava/parser/ParsePrimary.java b/src/main/java/org/perlonjava/parser/ParsePrimary.java index f994ad0a9..ee4f19e3d 100644 --- a/src/main/java/org/perlonjava/parser/ParsePrimary.java +++ b/src/main/java/org/perlonjava/parser/ParsePrimary.java @@ -214,6 +214,16 @@ static boolean isIsQuoteLikeOperator(String operator) { * @throws PerlCompilerException if the operator is not recognized or used incorrectly */ static Node parseOperator(Parser parser, LexerToken token, String operator) { + // Check for autoquoting: keyword operators before => should be treated as barewords + // This handles cases like: and => {...}, or => {...}, xor => {...} + if (operator.equals("and") || operator.equals("or") || operator.equals("xor")) { + String peekTokenText = peek(parser).text; + if (peekTokenText.equals("=>")) { + // Autoquote: convert operator keyword to string literal + return new StringNode(token.text, parser.tokenIndex); + } + } + Node operand = null; switch (token.text) { case "(": diff --git a/src/main/java/org/perlonjava/parser/PrototypeArgs.java b/src/main/java/org/perlonjava/parser/PrototypeArgs.java index 7f947cf03..74472c8b7 100644 --- a/src/main/java/org/perlonjava/parser/PrototypeArgs.java +++ b/src/main/java/org/perlonjava/parser/PrototypeArgs.java @@ -87,7 +87,7 @@ private static boolean allowsZeroArguments(String prototype) { private static boolean isArgumentTerminator(Parser parser) { var next = TokenUtils.peek(parser); return next.type == LexerTokenType.EOF || - ParserTables.LIST_TERMINATORS.contains(next.text) || + ListParser.isListTerminator(parser, next) || Parser.isExpressionTerminator(next) || // Assignment operators should terminate argument parsing next.text.equals("=") ||