Создание лексического анализатора
Я работаю с программой Lexical Analyzer прямо сейчас, и я использую Java. Я изучаю ответы на эту проблему, но до сих пор я не нашел их. Вот моя проблема:
Input:
System.out.println ("Hello World");
Требуемый выход:
Lexeme----------------------Token
System [Key_Word]
. [Object_Accessor]
out [Key_Word]
. [Object_Accessor]
println [Key_Word]
( [left_Parenthesis]
"Hello World" [String_Literal]
) [right_Parenthesis]
; [statement_separator]
Я все еще новичок, поэтому надеюсь, что вы, ребята, можете мне помочь в этом. Спасибо.
Ответы
Ответ 1
Вам не нужно ни АНТЛР, ни книга Дракона написать простой лексический анализатор вручную. Даже лексические анализаторы для более полных языков (например, Java) сложнее не писать вручную. Очевидно, что если у вас есть промышленная задача, вы можете подумать о промышленных силовых инструментах, таких как ANTLR или какой-нибудь лексический вариант, но для того, чтобы узнать, как работает лексический анализ, письмо, написанное вручную, вероятно, окажется полезным упражнением. Я предполагаю, что это так, поскольку вы сказали, что все еще новичок.
Вот простой лексический анализатор, написанный на Java, для подмножества языка типа Scheme, который я написал после просмотра этого вопроса. Я думаю, что код относительно легко понять, даже если вы никогда раньше не видели lexer, просто потому, что разбивали поток символов (в данном случае a String
) на поток токенов (в данном случае a List<Token>
) это не так сложно. Если у вас есть вопросы, я могу попытаться объяснить более подробно.
import java.util.List;
import java.util.ArrayList;
/*
* Lexical analyzer for Scheme-like minilanguage:
* (define (foo x) (bar (baz x)))
*/
public class Lexer {
public static enum Type {
// This Scheme-like language has three token types:
// open parens, close parens, and an "atom" type
LPAREN, RPAREN, ATOM;
}
public static class Token {
public final Type t;
public final String c; // contents mainly for atom tokens
// could have column and line number fields too, for reporting errors later
public Token(Type t, String c) {
this.t = t;
this.c = c;
}
public String toString() {
if(t == Type.ATOM) {
return "ATOM<" + c + ">";
}
return t.toString();
}
}
/*
* Given a String, and an index, get the atom starting at that index
*/
public static String getAtom(String s, int i) {
int j = i;
for( ; j < s.length(); ) {
if(Character.isLetter(s.charAt(j))) {
j++;
} else {
return s.substring(i, j);
}
}
return s.substring(i, j);
}
public static List<Token> lex(String input) {
List<Token> result = new ArrayList<Token>();
for(int i = 0; i < input.length(); ) {
switch(input.charAt(i)) {
case '(':
result.add(new Token(Type.LPAREN, "("));
i++;
break;
case ')':
result.add(new Token(Type.RPAREN, ")"));
i++;
break;
default:
if(Character.isWhitespace(input.charAt(i))) {
i++;
} else {
String atom = getAtom(input, i);
i += atom.length();
result.add(new Token(Type.ATOM, atom));
}
break;
}
}
return result;
}
public static void main(String[] args) {
if(args.length < 1) {
System.out.println("Usage: java Lexer \"((some Scheme) (code to) lex)\".");
return;
}
List<Token> tokens = lex(args[0]);
for(Token t : tokens) {
System.out.println(t);
}
}
}
Пример использования:
~/code/scratch $ java Lexer ""
~/code/scratch $ java Lexer "("
LPAREN
~/code/scratch $ java Lexer "()"
LPAREN
RPAREN
~/code/scratch $ java Lexer "(foo)"
LPAREN
ATOM<foo>
RPAREN
~/code/scratch $ java Lexer "(foo bar)"
LPAREN
ATOM<foo>
ATOM<bar>
RPAREN
~/code/scratch $ java Lexer "(foo (bar))"
LPAREN
ATOM<foo>
LPAREN
ATOM<bar>
RPAREN
RPAREN
Как только вы написали одну или две простые лексеры, подобные этому, вы получите довольно хорошее представление о том, как эта проблема распадается. Тогда было бы интересно изучить, как использовать автоматизированные инструменты, такие как lex. Теория подходов, основанных на регулярных выражениях, не слишком сложна, но для полного понимания требуется некоторое время. Я думаю, что писать лексеры вручную мотивирует это изучение и помогает вам справиться с проблемой лучше, чем погрузиться в теорию преобразования регулярных выражений в конечную автоматизацию (первые NFA, затем NFAs для DFA) и т.д.... wading в эту теорию может быть много, чтобы принять сразу, и легко получить перегружены.
Лично, в то время как книга Дракона хороша и очень тщательна, охват может быть не самым простым для понимания, потому что он стремится быть полным, не обязательно доступным. Возможно, вы захотите попробовать другие тексты компиляторов, прежде чем открывать книгу Дракона. Вот несколько бесплатных книг, которые имеют довольно хороший вводный охват, ИМХО:
http://www.ethoberon.ethz.ch/WirthPubl/CBEAll.pdf
http://www.diku.dk/~torbenm/Basics/
Некоторые статьи о реализации регулярных выражений (автоматический лексический анализ обычно использует регулярные выражения)
http://swtch.com/~rsc/regexp/
Надеюсь, это поможет. Удачи.
Ответ 2
ANTLR 4 будет делать именно это с помощью справочной грамматики Java.g4
. У вас есть два варианта в зависимости от того, насколько вы хотите, чтобы обработка управляющих последовательностей Unicode соответствовала спецификации языка.
Изменить: имена токенов, созданных этой грамматикой, немного отличаются от вашей таблицы.
- Ваш токен
Key_Word
Identifier
- Ваш токен
Object_Accessor
DOT
- Ваш токен
left_Parenthesis
LPAREN
- Ваш токен
String_Literal
StringLiteral
- Ваш токен
right_Parenthesis
RPAREN
- Ваш токен
statement_separator
SEMI
Ответ 3
Лексический анализ - это тема сама по себе, которая обычно сочетается с дизайном и анализом компилятора. Вы должны прочитать об этом, прежде чем пытаться что-либо кодировать. Моя любимая книга на эту тему - это Dragon, которая должна дать вам хорошее представление о дизайне компилятора и даже предоставить псевдокоды для всех фаз компилятора, которые вы можете легко перевести на Java и перейти оттуда.
Короче говоря, основная идея состоит в том, чтобы проанализировать ввод и разделить его на токены, принадлежащие определенным классам (круглые скобки или ключевые слова, например, на вашем желаемом выходе) с использованием конечного конечного автомата. Процесс государственного машиностроения на самом деле является единственной трудной частью этого анализа, и книга Дракона предоставит вам отличное понимание этой вещи.
Ответ 4
В Java можно использовать библиотеки типа Lex & Bison
в C или Antlr
. Лексический анализ может быть сделан путем создания автоматов. Я приведу небольшой пример:
Предположим, вам нужно tokenize строку, где ключевые слова (язык) {'echo', '.', ' ', 'end')
. По ключевым словам я подразумеваю, что язык состоит только из следующих ключевых слов. Поэтому, если я ввожу
echo .
end .
Мой лексер должен выводить
echo ECHO
SPACE
. DOT
end END
SPACE
. DOT
Теперь, чтобы создать автоматы для такого токенизатора, я могу начать с
->(SPACE) (Back)
|
(S)-------------E->C->H->O->(ECHO) (Back)
| |
.->(DOT)(Back) ->N->D ->(END) (Back to Start)
Выше диаграммы очень плохо, но идея в том, что у вас есть начальное состояние, представленное S
, теперь вы потребляете E
и переходите в другое состояние, теперь вы ожидаете, что N
или C
появятся END
и ECHO
соответственно. Вы продолжаете употреблять символы и достигать разных состояний в этой простой машине с конечным состоянием. В конечном итоге вы достигаете определенного состояния Emit
, например, после потребления E
, N
, D
вы достигаете состояния emit для END
, который испускает токен, а затем возвращается в состояние start
. Этот цикл продолжается навсегда, поскольку у вас есть поток символов, поступающий на ваш токенизатор. При недопустимом знаке вы можете либо выбросить ошибку, либо проигнорировать в зависимости от дизайна.
Ответ 5
CookCC (https://github.com/coconut2015/cookcc) генерирует очень быстрый, небольшой лексир с нулевой зависимостью для Java.
Ответ 6
Напишите программу для создания простого лексического анализатора, который будет строить таблицу символов из заданного потока символов. Вам нужно будет прочитать файл с именем "input.txt", чтобы собрать все символы. Для простоты, входной файл будет программой на C/Java/Python без заголовков и методов (тело основной программы). Затем вы определите все числовые значения, идентификаторы, ключевые слова, математические операторы, логические операторы и другие [разные]. Смотрите пример для более подробной информации. Вы можете предположить, что после каждого ключевого слова будет пробел.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
/* By Ashik Rabbani
Daffodil International University,CSE43 */
keyword_check();
identifier_check();
math_operator_check();
logical_operator_check();
numerical_check();
others_check();
return 0;
}
void math_operator_check()
{
char ch, string_input[15], operators[] = "+-*/%";
FILE *fp;
char tr[20];
int i,j=0;
fp = fopen("input.txt","r");
if(fp == NULL){
printf("error while opening the file\n");
exit(0);
}
printf("\nMath Operators : ");
while((ch = fgetc(fp)) != EOF){
for(i = 0; i < 6; ++i){
if(ch == operators[i])
printf("%c ", ch);
}
}
printf("\n");
fclose(fp);
}
void logical_operator_check()
{
char ch, string_input[15], operators[] = "&&||<>";
FILE *fp;
char tr[20];
int i,j=0;
fp = fopen("input.txt","r");
if(fp == NULL){
printf("error while opening the file\n");
exit(0);
}
printf("\nLogical Operators : ");
while((ch = fgetc(fp)) != EOF){
for(i = 0; i < 6; ++i){
if(ch == operators[i])
printf("%c ", ch);
}
}
printf("\n");
fclose(fp);
}
void numerical_check()
{
char ch, string_input[15], operators[] ={'0','1','2','3','4','5','6','7','8','9'};
FILE *fp;
int i,j=0;
fp = fopen("input.txt","r");
if(fp == NULL){
printf("error while opening the file\n");
exit(0);
}
printf("\nNumerical Values : ");
while((ch = fgetc(fp)) != EOF){
for(i = 0; i < 6; ++i){
if(ch == operators[i])
printf("%c ", ch);
}
}
printf("\n");
fclose(fp);
}
void others_check()
{
char ch, string_input[15], symbols[] = "(){}[]";
FILE *fp;
char tr[20];
int i,j=0;
fp = fopen("input.txt","r");
if(fp == NULL){
printf("error while opening the file\n");
exit(0);
}
printf("\nOthers : ");
while((ch = fgetc(fp)) != EOF){
for(i = 0; i < 6; ++i){
if(ch == symbols[i])
printf("%c ", ch);
}
}
printf("\n");
fclose(fp);
}
void identifier_check()
{
char ch, string_input[15];
FILE *fp;
char operators[] ={'0','1','2','3','4','5','6','7','8','9'};
int i,j=0;
fp = fopen("input.txt","r");
if(fp == NULL){
printf("error while opening the file\n");
exit(0);
}
printf("\nIdentifiers : ");
while((ch = fgetc(fp)) != EOF){
if(isalnum(ch)){
string_input[j++] = ch;
}
else if((ch == ' ' || ch == '\n') && (j != 0)){
string_input[j] = '\0';
j = 0;
if(isKeyword(string_input) == 1)
{
}
else
printf("%s ", string_input);
}
}
printf("\n");
fclose(fp);
}
int isKeyword(char string_input[]){
char keywords[32][10] = {"auto","break","case","char","const","continue","default",
"do","double","else","enum","extern","float","for","goto",
"if","int","long","register","return","short","signed",
"sizeof","static","struct","switch","typedef","union",
"unsigned","void","volatile","while"};
int i, flag = 0;
for(i = 0; i < 32; ++i){
if(strcmp(keywords[i], string_input) == 0){
flag = 1;
break;
}
}
return flag;
}
void keyword_check()
{
char ch, string_input[15], operators[] = "+-*/%=";
FILE *fp;
char tr[20];
int i,j=0;
printf(" Token Identification using C \n By Ashik-E-Rabbani \n 161-15-7093\n\n");
fp = fopen("input.txt","r");
if(fp == NULL){
printf("error while opening the file\n");
exit(0);
}
printf("\nKeywords : ");
while((ch = fgetc(fp)) != EOF){
if(isalnum(ch)){
string_input[j++] = ch;
}
else if((ch == ' ' || ch == '\n') && (j != 0)){
string_input[j] = '\0';
j = 0;
if(isKeyword(string_input) == 1)
printf("%s ", string_input);
}
}
printf("\n");
fclose(fp);
}