Como um lexer deve lidar com instruções de várias linhas (por exemplo, definições de funções, instruções de fluxo de controle)?

5

tl; dr-ers:

Como um lexer normalmente lida com instruções não-inline? instruções que não terminam com um delimitador de instrução especificado. Tais como declarações de fluxo de controle?

Acredito que tenho uma boa compreensão da análise lexical, e posso seguir em frente na minha busca pela compreensão de lexers / parsers. No entanto, entendo como os lexers lidam com instruções de várias linhas 1 .

Depois de ler um artigo da Wikipedia sobre a Comparação da sintaxe das linguagens de programação , a coisa que todas as linguagens têm em comum, é que eles têm um delineamento de declaração muito específico. Alguns utilizaram ponto e vírgula ( ; ) como delimitador de instrução, outros usam espaço em branco ( \ws ) e alguns períodos usados ( . ).

Usando esse método, não consigo ver como essas linguagens de programação podem ter funções \ class \ flow de controle, que abrangem várias linhas. Corrija-me se estiver errado, mas tenho certeza de que a maioria das linguagens de programação populares (Python, Java, C, C ++, C #, Javascript, etc ...) não usa seus delimitadores de instrução no final das funções , ou classes (eu sei que as classes em C ++ usam ponto-e-vírgula no final, mas isso é além do ponto), ou fluxo de controle.

Isso significa que: A: Os lexers fazem uma exceção especial para instruções que abrangem várias linhas. Ou B: O lexer apenas trata as instruções de várias linhas como uma instrução regular, e é o trabalho do analisador fazer sentido delas.

Por exemplo, leve este pseudo programa em C ++:

int exampleVar; //<-- inline statement. Delimited with a semicolon

void exampleFunc() { //<-- multi-line statement. This statement is the start of a block.
    // do things
} //<-- this is where the statement that was started above, should end?

É claro que é fácil ver onde você deve terminar a primeira declaração. Você termina no ponto e vírgula. Mas como é a segunda declaração tratada? A declaração se estende para incluir tudo até a chave de fechamento?

Ou eu poderia estar enganado no meu pensamento. Pode ser que o léxico não tenha absolutamente nada a ver com instruções de várias linhas 1 . Este é o trabalho do analisador? Ou seja, é o trabalho do analisador entender as instruções de várias linhas 1 ?

Para o mais claro possível, minha pergunta é: Como (se deveriam, em primeiro lugar), um léxico deveria lidar com declarações que não são inline, e não podem ser terminadas como se fossem. Como um lexer normalmente lida com instruções in-line? instruções que não terminam com um delimitador de instrução especificado. Tais como declarações de fluxo de controle?

1 Para ser claro, não quero dizer declarações de várias linhas, no sentido de continuação de linha. Quero dizer, no sentido de declarações que começam em um bloco. Tal como uma declaração de função. Quando você está definindo uma função, você também precisa conhecer as instruções que seguem a definição da função até um determinado delimitador de bloco. Então você não pode simplesmente terminar a declaração após a definição.

    
por Christian Dean 28.09.2016 / 04:03
fonte

1 resposta

7

Como você já conjeturou, este não é o trabalho do léxico. Ele não é comercializado em termos de declarações, declarações e definições, mas em entidades de muito mais baixo nível chamadas de tokens .

Por exemplo, para a seguinte função C,

static int
sum_plus_42(const int a, const int b)
{
  int result = a + b + 42;
  return result;
}

o lexer produziria a seguinte sequência de tokens.

  • palavra-chave static
  • palavra-chave int
  • identificador sum_plus_42
  • parêntese de abertura
  • palavra-chave const
  • palavra-chave int
  • identificador a
  • vírgula
  • palavra-chave const
  • palavra-chave int
  • identificador b
  • parêntese de fechamento
  • chave de abertura
  • palavra-chave int
  • identificador result
  • operador =
  • identificador a
  • operador +
  • identificador b
  • operador +
  • literal inteiro 42
  • ponto e vírgula
  • palavra-chave return
  • identificador result
  • ponto e vírgula
  • parêntese de fechamento

Se houvesse um erro de sintaxe no código (como parênteses sem correspondência), o lexer ficaria feliz se tokenizasse de qualquer maneira. No entanto, ele detectaria um erro léxico , como caracteres inválidos em um literal numérico, digamos 123wrong456 .

Após o código-fonte ter sido tokenizado, o analisador constrói a árvore de sintaxe a partir da sequência de tokens.

Tanto a análise lexical como a sintática são conduzidas por uma especificação de uma gramática formal e não há razão teórica para que estas não possam ser fundidas em uma única. Na prática, no entanto, faz um código mais limpo e estruturado para separar as duas etapas. A gramática para a análise lexical é geralmente muito mais simples e regular enquanto a gramática usada para descrever a sintaxe da linguagem é sem contexto . Na prática, as gramáticas geralmente não são tão limpas e têm mais ou menos casos especiais fora das regras formais das gramáticas regulares e livres de contexto.

Caso isso não esteja claro, os símbolos de terminal usados na gramática para a análise sintática são os símbolos produzidos pelo léxico, que são não terminais símbolos da sua própria gramática.

    
por 28.09.2016 / 04:31
fonte