我正在使用Lexical Analyzer程序,而我正在使用Java。我一直在研究这个问题的答案,但直到现在我都找不到。这是我的问题:
输入:
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]
我还是初学者,所以我希望你们可以帮助我。感谢。
答案 0 :(得分:45)
你既不需要ANTLR也不需要Dragon书来手工编写简单的词法分析器。即使是用于更全面的语言(如Java)的词法分析器,手工编写也不是非常复杂。显然,如果您有工业任务,您可能需要考虑像ANTLR或某些词法变体这样的工业强度工具,但为了学习词汇分析的工作原理,手工编写可能会证明是一项有用的练习。我假设情况就是这样,因为你说你还是初学者。
这是一个简单的词法分析器,用Java编写,用于类似Scheme的语言的一个子集,我在看到这个问题后写了这个。我认为代码相对容易理解,即使你之前从未见过词法分析器,只是因为将一串字符(在本例中为String
)分成一个标记流(在本例中为{{{ 1}})并不那么难。如果您有任何疑问,我可以尝试更深入地解释。
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);
}
}
}
一旦你写了一两个这样的简单词法分析器,你就会很清楚这个问题是如何分解的。然后探索如何使用像lex这样的自动化工具会很有趣。基于正则表达式的匹配器背后的理论并不太难,但确实需要一段时间才能完全理解。我认为手工编写词法分析器可以激发这项研究,并帮助你更好地掌握这个问题,而不是深入研究将正则表达式转换为有限自动机(首先是NFA,然后是NFA到DFA)等理论......趟过这个理论可以要立刻吸收很多东西,很容易被淹没。
就个人而言,虽然龙书很好而且非常彻底,但报道可能并不是最容易理解的,因为它的目的是完整,不一定是可访问的。在打开Dragon书之前,您可能想尝试一些其他编译器文本。这里有一些免费的书,有很好的介绍性报道,恕我直言:
http://www.ethoberon.ethz.ch/WirthPubl/CBEAll.pdf
http://www.diku.dk/~torbenm/Basics/
关于正则表达式实现的一些文章(自动词法分析通常使用正则表达式)
我希望有所帮助。祝你好运。
答案 1 :(得分:5)
ANTLR 4将使用Java.g4
引用语法完成此操作。您有两个选项,具体取决于您希望Unicode转义序列的处理遵循语言规范的程度。
ANTLRInputStream
包装在JavaUnicodeInputStream
中,该{{3}}根据JLS事先处理Unicode转义序列将它们喂给词法分析器。编辑:此语法生成的令牌名称与您的表略有不同。
Key_Word
令牌为Identifier
Object_Accessor
令牌为DOT
left_Parenthesis
令牌为LPAREN
String_Literal
令牌为StringLiteral
right_Parenthesis
令牌为RPAREN
statement_separator
令牌为SEMI
答案 2 :(得分:2)
词法分析本身就是一个主题,通常与编译器设计和分析结合在一起。在尝试编写任何代码之前,您应该阅读它。我最喜欢的关于这个主题的书是Dragon书,它可以为你提供编译器设计的一个很好的介绍,甚至为你可以轻松转换为Java并从那里移动的所有编译器阶段提供伪代码。
简而言之,主要思想是解析输入并使用有限状态机将其划分为属于某些类(括号或关键字,例如,在您所需的输出中)的标记。国家机器制造过程实际上是本分析中唯一的难点,龙书将为您提供对此事物的深刻见解。
答案 3 :(得分:2)
您可以使用C中的Lex & Bison
或Java中的Antlr
等库。词法分析可以通过制作自动机来完成。我给你一个小例子:
假设您需要对关键字(语言)为{'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
后,您达到END
的发送状态,然后发出令牌然后你回到start
州。只要你有字符串流进入你的tokenizer,这个循环就会一直持续下去。在无效字符上,您可以抛出错误或忽略,具体取决于设计。
答案 4 :(得分:0)
CookCC(https://github.com/coconut2015/cookcc)为Java生成了一个非常快速,小型,零依赖的词法分析器。
答案 5 :(得分:-2)
编写一个程序来制作一个简单的词法分析器,该分析器将根据给定的字符流构建一个符号表。您将需要读取一个名为“ 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);
}