Como dito no título, que tipo de dados um lexer deve retornar / fornecer ao analisador? Ao ler o artigo de análise lexical que a Wikipedia possui, afirmou que:
Na ciência da computação, a análise lexical é o processo de conversão de uma sequência de caracteres (como em um programa de computador ou página da web) em uma sequência de tokens ( strings com um "significado" identificado).
No entanto, em completa contradição com a afirmação acima, quando outra pergunta que fiz em um site diferente ( Revisão do código, se você estiver curioso) foi respondida, a pessoa que respondeu afirmou que:
O lexer geralmente lê a string e a converte em um fluxo ... de lexemes. Os lexemas precisam apenas ser um fluxo de números .
e ele deu esse visual:
nl_output => 256
output => 257
<string> => 258
Mais adiante, no artigo, ele mencionou Flex
um lexer já existente e disse que escrever "regras" com ele seria mais simples do que escrever um lexer à mão. Ele passou a me dar este exemplo:
Space [ \r\n\t]
QuotedString "[^"]*"
%%
nl_output {return 256;}
output {return 257;}
{QuotedString} {return 258;}
{Space} {/* Ignore */}
. {error("Unmatched character");}
%%
Para aprofundar meus conhecimentos e obter mais informações, li o artigo da Wikipedia sobre o Flex . o artigo Flex mostrou que você pode definir um conjunto de regras de sintaxe, com tokens, da seguinte maneira:
digit [0-9]
letter [a-zA-Z]
%%
"+" { return PLUS; }
"-" { return MINUS; }
"*" { return TIMES; }
"/" { return SLASH; }
"(" { return LPAREN; }
")" { return RPAREN; }
";" { return SEMICOLON; }
"," { return COMMA; }
"." { return PERIOD; }
":=" { return BECOMES; }
"=" { return EQL; }
"<>" { return NEQ; }
"<" { return LSS; }
">" { return GTR; }
"<=" { return LEQ; }
">=" { return GEQ; }
"begin" { return BEGINSYM; }
"call" { return CALLSYM; }
"const" { return CONSTSYM; }
"do" { return DOSYM; }
"end" { return ENDSYM; }
"if" { return IFSYM; }
"odd" { return ODDSYM; }
"procedure" { return PROCSYM; }
"then" { return THENSYM; }
"var" { return VARSYM; }
"while" { return WHILESYM; }
Parece-me que o Flex lexer está retornando seqüências de palavras-chave \ tokens. Mas poderia estar retornando constantes iguais a determinados números.
Se o lexer retornasse números, como ele leria literais de string? retornar um número é adequado para palavras-chave únicas. Mas como você lida com uma string? O lexer não precisaria converter a string em números binários e, em seguida, o analisador converteria os números novamente em uma string. Parece muito mais lógico (e mais fácil) para o lexer retornar strings e, em seguida, permitir que o analisador converta qualquer literal de string numérico em números reais.
Ou o lexer poderia retornar os dois? Eu tenho tentado escrever um lexer simples em c ++, que permite que você tenha apenas um tipo de retorno para suas funções. Assim, levando-me a fazer minha pergunta.
Para condensar minha pergunta em um parágrafo: Ao escrever um lexer, e assumindo que ele poderia retornar apenas um tipo de dados (strings ou números), qual seria a opção mais lógica?
fonte
Respostas:
Geralmente, se você estiver processando um idioma através de lexing e análise, terá uma definição de seus tokens lexicais, por exemplo:
e você tem uma gramática para o analisador:
Seu lexer pega o fluxo de entrada e produz um fluxo de tokens. O fluxo de tokens é consumido pelo analisador para produzir uma árvore de análise. Em alguns casos, basta conhecer o tipo do token (por exemplo, LPAREN, RBRACE, FOR), mas em alguns casos, você precisará do valor real associado ao token. Por exemplo, quando você encontrar um token de ID, desejará os caracteres reais que compõem o ID mais tarde, quando estiver tentando descobrir qual identificador está tentando fazer referência.
Então, você normalmente tem algo mais ou menos assim:
Portanto, quando o lexer retorna um token, você sabe qual é o tipo (do qual você precisa para a análise) e a sequência de caracteres a partir da qual foi gerado (do qual você precisará mais tarde para interpretar cadeias e literais numéricos, identificadores, etc.) Pode parecer que você está retornando dois valores, já que está retornando um tipo agregado muito simples, mas realmente precisa das duas partes. Afinal, você gostaria de tratar os seguintes programas de maneira diferente:
Eles produzem a mesma sequência de tipos de token : SE, LPAREN, NÚMERO, MAIOR_THAN, NÚMERO, RPAREN, LBRACE, ID, LPAREN, STRING, RPAREN, SEMICOLON, RBRACE. Isso significa que eles analisam o mesmo também. Mas quando você estiver realmente fazendo algo com a árvore de análise, você se importará de que o valor do primeiro número seja '2' (ou '0') e que o valor do segundo número seja '0' (ou '2 ') e que o valor da sequência é' 2> 0 '(ou' 0> 2 ').
fonte
String value
preenchido? será preenchido com uma string ou um número? E também, como eu definiria oString
tipo?parse(inputStream).forEach(token -> print(token.string); print(' '))
(ou seja, basta imprimir os valores da string dos tokens, separados por espaço). Isso é bem rápido. E mesmo que o LPAREN possa surgir apenas de "(", isso pode ser uma cadeia constante na memória, incluir uma referência a ele no token pode não ser mais caro do que incluir a referência nula. Em geral, prefiro escrever código que não me torna um caso especial de código"Token", obviamente. Um lexer produz um fluxo de tokens, portanto, ele deve retornar um fluxo de tokens .
Os lexers gerados por máquina têm a vantagem de gerá-los rapidamente, o que é particularmente útil se você acha que sua gramática lexical vai mudar muito. Eles têm a desvantagem de que você geralmente não tem muita flexibilidade nas suas opções de implementação.
Dito isto, quem se importa se é "mais simples"? Escrever o lexer geralmente não é a parte mais difícil!
Nem. Um lexer normalmente tem uma operação "próxima" que retorna um token, portanto, ele deve retornar um token . Um token não é uma sequência ou um número. É um sinal.
O último lexer que escrevi foi um lexer de "fidelidade total", o que significa que ele retornou um token que rastreia a localização de todos os espaços em branco e comentários - que chamamos de "trivialidades" - no programa, bem como o token. No meu lexer, um token foi definido como:
Curiosidades foi definido como:
Então, se tivéssemos algo como
que seria lex como quatro fichas com tipos simbólicos
Identifier
,Plus
,Identifier
,Semicolon
, e as larguras de 3, 1, 3, 1. O primeiro identificador tem trivialidades que consiste em líderWhitespace
com uma largura de 4 e de fuga trivialidadesWhitespace
com largura de 1. APlus
não tem trivialidades que conduz e trivialidades finais consistindo em um espaço em branco, um comentário e uma nova linha. O identificador final tem uma trivialidade principal de um comentário e um espaço, e assim por diante.Com esse esquema, todos os caracteres do arquivo são contabilizados na saída do lexer, que é uma propriedade útil para itens como coloração de sintaxe.
Obviamente, se você não precisa de trivialidades, pode simplesmente fazer um token de duas coisas: o tipo e a largura.
Você pode perceber que o token e as trivialidades contêm apenas suas larguras, não sua posição absoluta no código-fonte. Isso é deliberado. Esse esquema tem vantagens:
Se você não se importa com nenhum desses cenários, um token pode ser representado como um tipo e um deslocamento, em vez de um tipo e uma largura.
Mas o principal argumento aqui é: programação é a arte de fazer abstrações úteis . Você está manipulando tokens; portanto, faça uma abstração útil sobre tokens e depois escolha por si mesmo quais detalhes de implementação estão subjacentes a ele.
fonte
Geralmente, você retorna uma pequena estrutura que possui um número que significa o token (ou valor da enumeração para facilitar o uso) e um valor opcional (sequência, ou possivelmente valor genérico / modelo). Outra abordagem seria retornar um tipo derivado para elementos que precisam transportar dados extras. Ambos são levemente desagradáveis, mas são soluções suficientes para um problema prático.
fonte
Token *
ou um simplesmente umToken
, ou umTokenPtr
que é um ponteiro compartilhado daToken
classe. Mas também vejo algum lexer retornar apenas um TokenType e armazenar o valor da string ou do número em outras variáveis globais ou estáticas. Outra pergunta é como podemos armazenar as informações de localização. Preciso ter uma estrutura de token que possua os campos TokenType, String e Location? Obrigado.struct Token {TokenType id; std::string lexeme; int line; int column;}
, certo? Para uma função pública do Lexer, comoPeekToken()
, a função pode retornar umToken *
ouTokenPtr
. Eu acho que por um tempo, se a função retornar o TokenType, como o Analisador tenta obter as outras informações sobre o Token? Portanto, um ponteiro como o tipo de dados é preferido para retornar dessa função. Algum comentário sobre a minha ideia? Graças