package ru.bmstu.datalog.algo;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import ru.bmstu.datalog.data.Argument;
import ru.bmstu.datalog.data.DatalogData;
import ru.bmstu.datalog.data.Fact;
import ru.bmstu.datalog.data.Predicate;
import ru.bmstu.datalog.data.Request;
import ru.bmstu.datalog.data.Rule;
/**
* Datalog parser based on the recursive descent.
* @author art-vybor
*/
public class DatalogParser {
private static int currentSymbol;
private static String stringForParse;
/**
* Parsing datalog program from file.
* @param filename
* @return {@link DatalogData}
* @throws Exception
*/
public static DatalogData parseDatalogFromFile(String filename) {
StringBuilder strBuilder = new StringBuilder();
try {
@SuppressWarnings("resource")
BufferedReader in = new BufferedReader(new FileReader(filename));
String str;
while ((str = in.readLine()) != null) {
str = str.replaceAll("[ \t\n]", "");
if (str.length() >= 2 && str.charAt(0) == '/' && str.charAt(1) == '/') continue; //comments
strBuilder.append(str);
}
} catch (Exception e) {
e.printStackTrace();
}
return parseDatalogFromString(strBuilder.toString());
}
//TODO написать удаление комментариев в любом случае
/**
* Parsing datalog program presented as a string.
* @param datalogProgram
* @return {@link DatalogData}
* @throws Exception
*/
public static DatalogData parseDatalogFromString(String datalogProgram) {
currentSymbol = 0;
stringForParse = datalogProgram.replaceAll("[ \t\n]", "");
DatalogData answer = new DatalogData();
try {
answer = parseProgram();
} catch (Exception e) {
e.printStackTrace();
}
return answer;
}
/**
* Program ::= (Request | Rule | Fact)*
* @return {@link DatalogData}
* @throws Exception
*/
private static DatalogData parseProgram() throws Exception {
DatalogData data = new DatalogData();
while (currentSymbol < stringForParse.length()-1) {
int cur = currentSymbol;
try {
data.addRequest(parseQuery());
} catch (Exception eQuery) {
currentSymbol = cur;
try {
data.addRule(parseRule());
} catch (Exception eRulle) {
currentSymbol = cur;
data.addFact(parseFact());
}
}
}
return data;
}
/**
* Rule ::= Predicate :- Predicate (',' Predicate)*
* @return {@link Rule}
* @throws Exception
*/
private static Rule parseRule() throws Exception {
Rule rule = new Rule();
rule.setHead(parsePredicate());
parseString(":-");
rule.addToBody(parsePredicate());
while (skipSymbol(','))
rule.addToBody(parsePredicate());
parseSymbol('.');
return rule;
}
/**
* Request ::= '?' ':-' Predicate .
* @return {@link Request}
* @throws Exception
*/
private static Request parseQuery() throws Exception {
parseSymbol('?');
parseString(":-");
Request request = new Request(parsePredicate());
parseSymbol('.');
return request;
}
/**
* Fact ::= Predicate '.'
* @return {@link Fact}
* @throws Exception
*/
private static Fact parseFact() throws Exception {
Fact fact = new Fact(parsePredicate());
parseSymbol('.');
return fact;
}
/**
* Predicate ::= Name '(' Arguments ')'
* @return {@link Predicate}
* @throws Exception
*/
private static Predicate parsePredicate() throws Exception {
String name = parseName();
parseSymbol('(');
ArrayList<Argument> arguments = parseArguments();
parseSymbol(')');
return new Predicate(name, arguments);
}
/**
* Arguments ::= Term | Arguments ',' Term
* @return {@link ArrayList<Argument>}
* @throws Exception
*/
private static ArrayList<Argument> parseArguments() throws Exception {
ArrayList<Argument> answer = new ArrayList<Argument>();
int cur = currentSymbol;
try {
answer.add(parseTerm());
} catch(Exception e) {
currentSymbol = cur;
return answer;
}
while(skipSymbol(','))
answer.add(parseTerm());
return answer;
}
/**
* Term ::= Number | Name | '_'
* @return {@link Argument}
* @throws Exception
*/
private static Argument parseTerm() throws Exception {
Argument answer;
int cur = currentSymbol;
try {
answer = new Argument(parseNumber());
} catch(Exception e) {
currentSymbol = cur;
if (curSymbol() == '_') {
answer = new Argument("_", -1);
next();
} else {
boolean isConstant = Character.isLowerCase(curSymbol());
if (isConstant) {
answer = new Argument(parseName());
} else {
answer = new Argument(parseName(), -1);
}
}
}
return answer;
}
/**
* Name ::= Letter ArbitaryString
* @return {@link String}
* @throws Exception
*/
private static String parseName() throws Exception {
if (!Character.isLetter(curSymbol())) throw new Exception("Incorrect predicate name (name is empty)");
char firstChar = curSymbol();
next();
return firstChar + parseArbitraryString();
}
/**
* Number ::= Digit Digit*
* @return {@link String}
* @throws Exception
*/
private static String parseNumber() throws Exception {
StringBuilder strBuilder = new StringBuilder();
if (!Character.isDigit(curSymbol())) throw new Exception("Incorrect number (number is empty)");
strBuilder.append(curSymbol());
next();
while (Character.isDigit(curSymbol())) {
strBuilder.append(curSymbol());
next();
}
return strBuilder.toString();
}
/**
* ArbitraryString ::= (Letter | Digit)*
* @return {@link String}
*/
private static String parseArbitraryString() {
StringBuilder strBuilder = new StringBuilder();
while (Character.isLetterOrDigit(curSymbol())) {
strBuilder.append(curSymbol());
next();
}
return strBuilder.toString();
}
/**
* Support functions
*/
private static char curSymbol() {
return stringForParse.charAt(currentSymbol);
}
private static boolean skipSymbol(char symbol) {
if (curSymbol() != symbol)
return false;
next();
return true;
}
private static boolean skipString(String str) {
for (int i = 0; i < str.length(); ++i) {
if (!skipSymbol(str.charAt(i)));
}
return true;
}
private static void parseSymbol(char symbol) throws Exception {
if (!skipSymbol(symbol)) throw new Exception("No symbol: '" + symbol + "'");
}
private static void parseString(String str) throws Exception {
if (!skipString(str)) throw new Exception("No string: '" + str + "'");
}
private static void next() {
currentSymbol++;
}
}