
Пришло время затронуть тему создания парсеров на java. Для этого воспользуемся утилитой javacc.
Update. Плагин Вордпресса для подсвечивания кода стал безнадежно портить исходники - добавлять закрывающие теги там, где они не нужны, дефолтные значения у того, что он считает атрибутами тегов. Неискаженные исходники, адаптированные к javaCC версии 7.0.9 доступны тут:
исходники
За основу взята эта статья с примерами:
источник моего вдохновения. В коде примеров исправлены ошибки и теперь они даже собираются 😉
Код доработан и теперь, к примеру, правильно обрабатывает запятую в перечислении: она допустима тогда и только тогда, когда за текущим элементом перечисления есть еще один или несколько элементов. Если для типа данных в скобках указана длина (опциональный параметр), то она тоже будет сохранена в результирующей структуре для описания типа.
Для простоты я закомментировал название пакета — если надо, раскоментируйте и ставьте свой.
Я собирал с помощью make:
JAVACC_JAR = /home/dk/javacc_6_1/javacc-6.1.0/target/javacc-6.1.0.jar
SOURCE_NAME=grammar
all: TableStruct.java TypeDesc.java ParserDemoTest.java SqlParser.java
javac *.java
SqlParser.java : $(SOURCE_NAME).jj
java -cp $(JAVACC_JAR) javacc $(SOURCE_NAME).jj
clean:
rm -rf ./ParseException.*
rm -rf ./SimpleCharStream.*
rm -rf ./SqlParser.*
rm -rf ./SqlParserConstants.*
rm -rf ./SqlParserTokenManager.*
rm -rf ./Token.*
rm -rf ./TokenMgrError.*
rm -rf ./*.class |
JAVACC_JAR = /home/dk/javacc_6_1/javacc-6.1.0/target/javacc-6.1.0.jar
SOURCE_NAME=grammar
all: TableStruct.java TypeDesc.java ParserDemoTest.java SqlParser.java
javac *.java
SqlParser.java : $(SOURCE_NAME).jj
java -cp $(JAVACC_JAR) javacc $(SOURCE_NAME).jj
clean:
rm -rf ./ParseException.*
rm -rf ./SimpleCharStream.*
rm -rf ./SqlParser.*
rm -rf ./SqlParserConstants.*
rm -rf ./SqlParserTokenManager.*
rm -rf ./Token.*
rm -rf ./TokenMgrError.*
rm -rf ./*.class
Все исходники лежат в текущей папке и название пакета не нужно.
Разбирать будем файлы вида:
##JavaccParserExample#####
CREATE TABLE STUDENT
(
StudentName varchar (20),
Class varchar(10),
Rnum integer
)
CREATE TABLE BOOKS
(
BookName varchar(10),
Edition integer,
Stock integer
)
CREATE TABLE CODES
(
StudentKey varchar(20),
StudentCode varchar(40)
) |
##JavaccParserExample#####
CREATE TABLE STUDENT
(
StudentName varchar (20),
Class varchar(10),
Rnum integer
)
CREATE TABLE BOOKS
(
BookName varchar(10),
Edition integer,
Stock integer
)
CREATE TABLE CODES
(
StudentKey varchar(20),
StudentCode varchar(40)
)
В первой строке мы видим комментарий, который будет игнорироваться благодаря этой инструкции:
SPECIAL_TOKEN : {<comment: ("#")+(<tname="">)+("#")+>}
</comment:> |
SPECIAL_TOKEN : {<comment: ("#")+(<tname="">)+("#")+>}
</comment:>
Далее в методе init() мы перебираем команды для создания таблиц, их может быть 0 и более — это задается конструкцией (...)*. В коде есть несколько закоментированных команд вывода в стандартный поток, они помогают понять что и когда вызывается.
В методе Variables() мы обрабатываем перечисление описаний столбцов при создании таблицы. Тут появляется на сцене рекурсия: перечесление может быть описанием одной переменной без запятой на конце, либо перечислением + запятая + описание. Вообще обработка отличается от обработки в yacc.
В общем случае в фигурных скобках задаются действия, которые надо выполнить при встрече в потоке определенных токенов или их последовательности:
(
TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("new struct populated < " + TName.image + ">");*/}
( <comma> TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("[Recursion] new struct populated < " + TName.image + ">");*/}
)*
)
</tname></comma></tname> |
(
TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("new struct populated < " + TName.image + ">");*/}
( <comma> TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("[Recursion] new struct populated < " + TName.image + ">");*/}
)*
)
</tname></comma></tname>
Т.е. мы можем встретить имя с описанием всего один раз и тогда сразу заносим это в коллекцию. В случае, если у нас идет перечисление с запятыми, то мы каждую также заносим в коллекцию.
Длина типа данных является необязательной и это отражено в соответствующем блоке кода (квадратные скобки для необязательной конструкции):
(
TDType=<tname>
[<obra>length=<number><cbra>]
)
</cbra></number></obra></tname> |
(
TDType=<tname>
[<obra>length=<number><cbra>]
)
</cbra></number></obra></tname>
Признаком того, что тип указан не был, является null в переменной length.
Для получения строкового представления токена нужно запросить его свойство image:
TDType.image
Полное содержание файла grammar.jj:
/* demo grammar.jj*/
options
{
STATIC = false;
}
PARSER_BEGIN (SqlParser)
//package ru.outofrange.javacc.createtable;
import java.util.ArrayList;
import java.util.HashMap;
class SqlParser {
ArrayList<tablestruct> initParser() throws ParseException, TokenMgrError {
return(init()) ;
}
}
PARSER_END (SqlParser)
SKIP: { "\n" | "\r" | "\r\n" | "\\" | "\t" | " "}
TOKEN [IGNORE_CASE]:
{
<ctcmd :("create="" table")="">
|<number :(["0"-"9"])+="">
|<tname: (["a"-"z"])+="">
|<obra: ("(")+="">
|<cbra: (")")+="">
|<comma: (",")="">
}
SPECIAL_TOKEN : {<comment: ("#")+(<tname="">)+("#")+>}
ArrayList<tablestruct> init():
{
Token T;
ArrayList<tablestruct> tableList = new ArrayList<tablestruct>();
TableStruct tableStruct;
}
{
(
<ctcmd>
T =<tname>
{ tableStruct = new TableStruct ();
tableStruct.TableName = T.image ;}
<obra>
tableStruct.Variables = Variables()
<cbra>
{tableList.add (tableStruct) ; /*System.out.println("new variable desc added");*/}
)*
<eof>
{return tableList;}
}
HashMap<string ,typedesc=""> Variables():
{
Token TName;
TypeDesc typeDesc;
HashMap <string, typedesc=""> var = new HashMap <string, typedesc="">();
}
{
(
TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("new struct populated < " + TName.image + ">");*/}
( <comma> TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("[Recursion] new struct populated < " + TName.image + ">");*/}
)*
)
{return var;}
}
TypeDesc DType():
{
Token TDType;
TypeDesc typeDesc;
Token length = null;
}
{
(
TDType=<tname>
[<obra>length=<number><cbra>]
)
{
typeDesc = new TypeDesc();
typeDesc.setTypeName(TDType.image);
if (length != null) {
typeDesc.setTypeLength(length.image); /*System.out.println("Lenght detected < " + length.image + ">");*/
}
return typeDesc;
}
}
</cbra></number></obra></tname></tname></comma></tname></string,></string,></string></eof></cbra></obra></tname></ctcmd></tablestruct></tablestruct></tablestruct></comment:></comma:></cbra:></obra:></tname:></number></ctcmd></tablestruct> |
/* demo grammar.jj*/
options
{
STATIC = false;
}
PARSER_BEGIN (SqlParser)
//package ru.outofrange.javacc.createtable;
import java.util.ArrayList;
import java.util.HashMap;
class SqlParser {
ArrayList<tablestruct> initParser() throws ParseException, TokenMgrError {
return(init()) ;
}
}
PARSER_END (SqlParser)
SKIP: { "\n" | "\r" | "\r\n" | "\\" | "\t" | " "}
TOKEN [IGNORE_CASE]:
{
<ctcmd :("create="" table")="">
|<number :(["0"-"9"])+="">
|<tname: (["a"-"z"])+="">
|<obra: ("(")+="">
|<cbra: (")")+="">
|<comma: (",")="">
}
SPECIAL_TOKEN : {<comment: ("#")+(<tname="">)+("#")+>}
ArrayList<tablestruct> init():
{
Token T;
ArrayList<tablestruct> tableList = new ArrayList<tablestruct>();
TableStruct tableStruct;
}
{
(
<ctcmd>
T =<tname>
{ tableStruct = new TableStruct ();
tableStruct.TableName = T.image ;}
<obra>
tableStruct.Variables = Variables()
<cbra>
{tableList.add (tableStruct) ; /*System.out.println("new variable desc added");*/}
)*
<eof>
{return tableList;}
}
HashMap<string ,typedesc=""> Variables():
{
Token TName;
TypeDesc typeDesc;
HashMap <string, typedesc=""> var = new HashMap <string, typedesc="">();
}
{
(
TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("new struct populated < " + TName.image + ">");*/}
( <comma> TName = <tname>
typeDesc = DType()
{var.put(TName.image, typeDesc); /*System.out.println("[Recursion] new struct populated < " + TName.image + ">");*/}
)*
)
{return var;}
}
TypeDesc DType():
{
Token TDType;
TypeDesc typeDesc;
Token length = null;
}
{
(
TDType=<tname>
[<obra>length=<number><cbra>]
)
{
typeDesc = new TypeDesc();
typeDesc.setTypeName(TDType.image);
if (length != null) {
typeDesc.setTypeLength(length.image); /*System.out.println("Lenght detected < " + length.image + ">");*/
}
return typeDesc;
}
}
</cbra></number></obra></tname></tname></comma></tname></string,></string,></string></eof></cbra></obra></tname></ctcmd></tablestruct></tablestruct></tablestruct></comment:></comma:></cbra:></obra:></tname:></number></ctcmd></tablestruct>
Вспомогательные классы:
//package ru.outofrange.javacc.createtable;
public class TypeDesc {
private String typeNameName;
private Integer typeLength = null;
public void setTypeLength(Integer typeLength){
this.typeLength = typeLength;
}
public void setTypeLength(String typeLength){
this.typeLength = Integer.valueOf(typeLength);
}
public void setTypeName(String typeNameName){
this.typeNameName = typeNameName;
}
public String getTypeName(){
return typeNameName;
}
public Integer getTypeLength(){
return typeLength;
}
} |
//package ru.outofrange.javacc.createtable;
public class TypeDesc {
private String typeNameName;
private Integer typeLength = null;
public void setTypeLength(Integer typeLength){
this.typeLength = typeLength;
}
public void setTypeLength(String typeLength){
this.typeLength = Integer.valueOf(typeLength);
}
public void setTypeName(String typeNameName){
this.typeNameName = typeNameName;
}
public String getTypeName(){
return typeNameName;
}
public Integer getTypeLength(){
return typeLength;
}
}
//package ru.outofrange.javacc.createtable;
import java.util.HashMap;
import java.util.Map;
public class TableStruct {
String TableName;
Map<string, typedesc=""> Variables =
new HashMap<string, typedesc=""> ();
}
</string,></string,> |
//package ru.outofrange.javacc.createtable;
import java.util.HashMap;
import java.util.Map;
public class TableStruct {
String TableName;
Map<string, typedesc=""> Variables =
new HashMap<string, typedesc=""> ();
}
</string,></string,>
Класс с функцией main() для тестирования парсера:
/*for testing the parser class*/
//package ru.outofrange.javacc.createtable;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Map;
public class ParserDemoTest {
public static void main(String[] args) {
try{
if (args.length < 1) {
return;
}
String filePath = args[0];
SqlParser parser = new SqlParser (new FileReader(filePath));
ArrayList<tablestruct> tableList = parser.initParser();
for(TableStruct t1 : tableList) {
System.out.println("--------------------------");
System.out.println("Table Name : " + t1.TableName);
for (Map.Entry<Ыtring ,TypeDesc> entry: t1.Variables.entrySet()){
System.out.print("Field name: " + entry.getKey() + " Data Type: " + entry.getValue().getTypeName());
if (entry.getValue().getTypeLength() != null) {
System.out.println(" Length: " + entry.getValue().getTypeLength());
} else {
System.out.println(" Length: <not specified="">");
}
}
System.out.println("--------------------------");
}
} catch (Exception ex) {
ex.printStackTrace() ;}
}
}
</not></tablestruct> |
/*for testing the parser class*/
//package ru.outofrange.javacc.createtable;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Map;
public class ParserDemoTest {
public static void main(String[] args) {
try{
if (args.length < 1) {
return;
}
String filePath = args[0];
SqlParser parser = new SqlParser (new FileReader(filePath));
ArrayList<tablestruct> tableList = parser.initParser();
for(TableStruct t1 : tableList) {
System.out.println("--------------------------");
System.out.println("Table Name : " + t1.TableName);
for (Map.Entry<Ыtring ,TypeDesc> entry: t1.Variables.entrySet()){
System.out.print("Field name: " + entry.getKey() + " Data Type: " + entry.getValue().getTypeName());
if (entry.getValue().getTypeLength() != null) {
System.out.println(" Length: " + entry.getValue().getTypeLength());
} else {
System.out.println(" Length: <not specified="">");
}
}
System.out.println("--------------------------");
}
} catch (Exception ex) {
ex.printStackTrace() ;}
}
}
</not></tablestruct>
Результат выполнения программы:
java ParserDemoTest ./sql_test
--------------------------
Table Name : STUDENT
Field name: Rnum Data Type: integer Length: <not specified="">
Field name: Class Data Type: varchar Length: 10
Field name: StudentName Data Type: varchar Length: 20
--------------------------
--------------------------
Table Name : BOOKS
Field name: BookName Data Type: varchar Length: 10
Field name: Edition Data Type: integer Length: <not specified="">
Field name: Stock Data Type: integer Length: <not specified="">
--------------------------
--------------------------
Table Name : CODES
Field name: StudentCode Data Type: varchar Length: 40
Field name: StudentKey Data Type: varchar Length: 20
--------------------------
</not></not></not> |
java ParserDemoTest ./sql_test
--------------------------
Table Name : STUDENT
Field name: Rnum Data Type: integer Length: <not specified="">
Field name: Class Data Type: varchar Length: 10
Field name: StudentName Data Type: varchar Length: 20
--------------------------
--------------------------
Table Name : BOOKS
Field name: BookName Data Type: varchar Length: 10
Field name: Edition Data Type: integer Length: <not specified="">
Field name: Stock Data Type: integer Length: <not specified="">
--------------------------
--------------------------
Table Name : CODES
Field name: StudentCode Data Type: varchar Length: 40
Field name: StudentKey Data Type: varchar Length: 20
--------------------------
</not></not></not>