Ошибка синтаксиса, когда map() возвращает LIST

Это работает,

print map { $_." x" => $_ } 1..5;
print map { ("$_ x" => $_) } 1..5;
print map { ("$_ x") => $_ } 1..5;

но это вызывает синтаксическую ошибку,

print map { "$_ x" => $_ } 1..5;

Является ли эта документированная ошибка, недокументированная ошибка, или я не понимаю, почему это не должно компилироваться?

Почему perl считает, что это должно быть map EXPR, LIST вместо map BLOCK LIST

Ответ 1

Почему perl считает, что это должно быть map EXPR, LIST вместо map BLOCK LIST?

Соответствующий раздел кода находится в toke.c, Perl lexer (ниже из Perl 5.22.0):

/* This hack serves to disambiguate a pair of curlies
 * as being a block or an anon hash.  Normally, expectation
 * determines that, but in cases where we're not in a
 * position to expect anything in particular (like inside
 * eval"") we have to resolve the ambiguity.  This code
 * covers the case where the first term in the curlies is a
 * quoted string.  Most other cases need to be explicitly
 * disambiguated by prepending a "+" before the opening
 * curly in order to force resolution as an anon hash.
 *
 * XXX should probably propagate the outer expectation
 * into eval"" to rely less on this hack, but that could
 * potentially break current behavior of eval"".
 * GSAR 97-07-21
 */
t = s;
if (*s == '\'' || *s == '"' || *s == '`') {
    /* common case: get past first string, handling escapes */
    for (t++; t < PL_bufend && *t != *s;)
        if (*t++ == '\\')
            t++;
    t++;
}
else if (*s == 'q') {
    if (++t < PL_bufend
        && (!isWORDCHAR(*t)
            || ((*t == 'q' || *t == 'x') && ++t < PL_bufend
                && !isWORDCHAR(*t))))
    {   
        /* skip q//-like construct */
        const char *tmps;
        char open, close, term;
        I32 brackets = 1;

        while (t < PL_bufend && isSPACE(*t))
            t++;
        /* check for q => */
        if (t+1 < PL_bufend && t[0] == '=' && t[1] == '>') {
            OPERATOR(HASHBRACK);
        }
        term = *t;
        open = term;
        if (term && (tmps = strchr("([{< )]}> )]}>",term)))
            term = tmps[5];
        close = term;
        if (open == close)
            for (t++; t < PL_bufend; t++) {
                if (*t == '\\' && t+1 < PL_bufend && open != '\\')
                    t++;
                else if (*t == open)
                    break;
            }
        else {
            for (t++; t < PL_bufend; t++) {
                if (*t == '\\' && t+1 < PL_bufend)
                    t++;
                else if (*t == close && --brackets <= 0)
                    break;
                else if (*t == open)
                    brackets++;
            }
        }
        t++;
    }
    else
        /* skip plain q word */
        while (t < PL_bufend && isWORDCHAR_lazy_if(t,UTF))
             t += UTF8SKIP(t);
}
else if (isWORDCHAR_lazy_if(t,UTF)) {
    t += UTF8SKIP(t);
    while (t < PL_bufend && isWORDCHAR_lazy_if(t,UTF))
         t += UTF8SKIP(t);
}
while (t < PL_bufend && isSPACE(*t))
    t++;
/* if comma follows first term, call it an anon hash */
/* XXX it could be a comma expression with loop modifiers */
if (t < PL_bufend && ((*t == ',' && (*s == 'q' || !isLOWER(*s)))
                   || (*t == '=' && t[1] == '>')))
    OPERATOR(HASHBRACK);
if (PL_expect == XREF)
{
  block_expectation:
    /* If there is an opening brace or 'sub:', treat it
       as a term to make ${{...}}{k} and &{sub:attr...}
       dwim.  Otherwise, treat it as a statement, so
       map {no strict; ...} works.
     */
    s = skipspace(s);
    if (*s == '{') {
        PL_expect = XTERM;
        break;
    }
    if (strnEQ(s, "sub", 3)) {
        d = s + 3;
        d = skipspace(d);
        if (*d == ':') {
            PL_expect = XTERM;
            break;
        }
    }
    PL_expect = XSTATE;
}
else {
    PL_lex_brackstack[PL_lex_brackets-1] = XSTATE;
    PL_expect = XSTATE;
}

Объяснение

Если первое слагаемое после открытия курсивом является строкой (с разделителем ', " или `)), либо с начальным словом, начинающимся с заглавной буквы, а следующий термин , или =>, курчавый рассматривается как начало анонимного хэша (что означает OPERATOR(HASHBRACK);).

Другие случаи для меня немного сложнее понять. Я выполнил следующую программу через gdb:

{ (x => 1) }

и закончил в последнем блоке else:

else {
    PL_lex_brackstack[PL_lex_brackets-1] = XSTATE;
    PL_expect = XSTATE;
}

Достаточно сказать, что путь выполнения явно отличается; он заканчивается анализом в виде блока.

Ответ 2

Из perlref

Поскольку фигурные скобки (фигурные скобки) используются для нескольких других вещей, включая БЛОКИ, иногда вам может понадобиться устранить фигурные скобки в начале заявления, положив + или вернуться вперед, чтобы Perl реализовал открытую скобу, а не начиная БЛОК. Экономическая и мнемоническая ценность использования завитушек считается такой случайной дополнительной неприятностью.

Чтобы сделать ваши намерения более ясными и помочь синтаксическому анализатору,

  • Скажите +{...} однозначно указать хеш-ссылку

    @list_of_hashrefs = map +{ "$_ x" => $_ }, 1..5;
    
  • Скажите {; ...} однозначно указать кодовый блок

    %mappings = map {; "$_ x" => $_ } 1..5;