Se eu tiver o sinal de menos ao lado de uma variável como:
int a;
int b = -a; // UNARY OPERATOR
b = - a; // UNARY OPERATOR
O menos antes do 'a' é considerado um operador unário e o valor negativo de a é tomado. No entanto, neste:
int a, b;
a -b; // SUBTRACTION
a - b; // SUBTRACTION
Então, disso deduzo que:
- É irrelevante se o operador está ou não separado do operando por um espaço.
- A dedução de se é uma subtração ou um operador unário depende da presença de um operando anterior e é altamente contextual.
Alguém pode dar um resumo simples das regras de como o compilador decide isso?
A teoria sobre análise sintática e linguagens formais é abordada em vários cursos de ciência da computação, então apenas uma pequena introdução pode ser fornecida em uma resposta do Stack Overflow. Compiladores podem usar um analisador LALR .
Para dar uma breve visão disto, há essencialmente uma lista de padrões de coisas que podem aparecer na linguagem que está sendo compilada. Por exemplo, em C, uma expressão aditiva é uma de:
+
expressão-multiplicativa-
expressão-multiplicativaCada um dos itens na regra pode ser um token para outro padrão, como expressão multiplicativa , ou um símbolo terminal, como
+
ouint
.Conforme o compilador lê o código-fonte, ele tenta combinar o texto que vê com padrões. Quando ele encontra uma correspondência adequada, ele reduz os itens que correspondem ao token cujo padrão eles corresponderam. Então
a
-
b
será reduzido para additional-expression , e nunca corresponderá a um padrão unary-expression-
b
. Enquanto sem outro additional-expression antes será reduzido para unary-expression e nunca corresponderá a um padrão additional-expression .Novamente, essa é uma visão simplificada de parte do processo. Antes mesmo de o compilador processar tokens na gramática, ele realiza uma análise lexical que agrupa caracteres em tokens gramaticais. Então, transformar
int foo;
em “palavra-chaveint
” e “identificadorfoo
” e transformara---b
em “identificadora
”, “operador--
”, “operador-
” e “identificadorb
” acontece em um nível anterior/inferior de análise. E há também, conceitualmente pelo menos, um nível de pré-processador separado.Além disso, o compilador não tem realmente os padrões listados, e não está diretamente combinando sequências de entrada com os padrões. Em vez disso, as regras gramaticais são usadas para construir uma “máquina” conceitual que implementa essa correspondência de padrões. Essa máquina é chamada de parser, e é construída no compilador. Cada novo token de entrada altera o estado da máquina, e a máquina é projetada de tal forma que os estados correspondem ao reconhecimento dos padrões.
Há matemática formal que mostra como gramáticas formais desse tipo podem ser usadas para construir “máquinas” (modelos matemáticos de computação) que realizam a análise sintática. Para entender isso completamente, deve-se fazer cursos em matemática discreta, máquinas de estados finitos e autômatos, linguagens formais (teoria da análise sintática) e construção de compiladores.
O primeiro passo da compilação é uma passagem com pré-processador de texto. O pré-processador divide o texto de entrada em tokens e se o token corresponder a um nome de macro, ele executa a substituição de token até que não haja mais tokens para substituir. O importante é que o pré-processador é ganancioso, ou seja, ao extrair um token, ele tenta extrair um token de comprimento máximo possível. Símbolos de espaço em branco fora dos literais de string sempre agem como separadores de token.
a+b
é tokenizado paraa
+
b
a++b
é tokenizado paraa
++
b
(ganancioso!)a+ +b
é tokenizado paraa
+
+
b
(o espaço é relevante!)a+++b
é tokenizado paraa
++
+
b
(novamente ganancioso!)O próximo compilador tenta descobrir o significado da sequência de tokens, dependendo do contexto. Se
+
aparecer como o primeiro token da (sub) expressão, então ele é assumido como um operador unário, caso contrário, ele é assumido como o binário+
. O próximo token é assumido como um primeiro token da próxima subexpressão.Em relação à parte do pré-processador da pergunta:
A forma como a expressão aparece é decidida no início do pré-processamento, conforme descrito em C23 6.4.1, que também contém a definição formal do termo espaço em branco :
Segue-se então o que os programadores C chamam informalmente de "regra da mastigação máxima":
Em inglês simples: durante o pré-processamento, o pré-processador percorre um pedaço de texto (da esquerda para a direita) e tenta formar a sequência mais longa de caracteres que formaria um dos vários itens mencionados, como identificadores como
a
eb
no seu exemplo. Mas tambémint
por enquanto, porque ainda não tem um significado especial durante o pré-processamento. Quando posteriormente traduzido para um token ,int
é categorizado como uma palavra-chave de idioma .=
,-
e;
são pontuadores conforme definido em 6.4.7. Por exemplo-=
é um pontuador válido, então se estivesse presente em uma expressão comoa-=b
, a "regra do máximo" teria sido lida-=
como um token de pré-processador.=-
entretanto não é um pontuador válido em si mesmo, então é lido como dois diferentes:=
e-
.E entre todos eles pode haver espaço em branco, que é usado para separar tokens do pré-processador, mas depois descartado.
Foi assim que todo o código foi analisado em tokens do pré-processador. Mais tarde, durante a compilação, quando os itens são traduzidos para tokens, eles começam a ter significado de acordo com a gramática da linguagem. Isso acontece na "fase de tradução 7", 5.2.1.2: