package de.fuberlin.projectci; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import de.fuberlin.bii.lexergen.BuilderType; import de.fuberlin.bii.lexergen.Lexergen; import de.fuberlin.bii.tokenmatcher.errorhandler.ErrorCorrector.CorrectionMode; import de.fuberlin.commons.lexer.ILexer; import de.fuberlin.commons.parser.IParser; import de.fuberlin.commons.parser.ISyntaxTree; import de.fuberlin.commons.util.LogFactory; import de.fuberlin.projecta.lexer.Lexer; import de.fuberlin.projecta.lexer.io.FileCharStream; import de.fuberlin.projecta.lexer.io.ICharStream; import de.fuberlin.projectci.grammar.BNFGrammarReader; import de.fuberlin.projectci.grammar.BNFParsingErrorException; import de.fuberlin.projectci.grammar.Grammar; import de.fuberlin.projectci.grammar.GrammarReader; import de.fuberlin.projectci.lrparser.LRParser; import de.fuberlin.projectci.lrparser.LRParserException; public class LRParserMain implements IParser{ private static Logger logger = LogFactory.getLogger(LRParserMain.class); private static final String VERSION="LRParser V0.1"; private static final String DEFAULT_GRAMMAR_FILE = "input/de/fuberlin/projectci/non-ambigous.txt"; private static final String DEFAULT_TOKEN_DEFINITION_FILE = "input/de/fuberlin/bii/def/tokendefinition.rd"; private static final String DEFAULT_LEXER="bi"; private static final Level DEFAULT_LOG_LEVEL_CONSOLE = Level.INFO; private static final Level DEFAULT_LOG_LEVEL_FILE = Level.ALL; private ISyntaxTree syntaxTree=null; private boolean reduceToAbstractSyntaxTree=false; private boolean removeEpsilonNodes=true; // Der SemanticAnalyzer erwartet einen Parsebaum ohne Epsilon-Knoten private boolean displayParseTableGui=false; /** * Fassade des LRParsers zur Außenwelt. */ public LRParserMain() { } /** * Ermöglicht das Reduzieren des erzeugten SyntaxTree durch "Hochziehen aller Einzelkinder". * @param reduceToAbstractSyntaxTree */ public void setReduceToAbstractSyntaxTree(boolean reduceToAbstractSyntaxTree) { this.reduceToAbstractSyntaxTree = reduceToAbstractSyntaxTree; } /** * Ermöglicht das Entfernen der Epsilon-Knoten aus dem erzeugten SyntaxTree * @param removeEpsilonNodes */ public void setRemoveEpsilonNodes(boolean removeEpsilonNodes) { this.removeEpsilonNodes = removeEpsilonNodes; } /** * Öffnet eine Swing-GUI zum Darstellen der Parsetabelle mit ACTION- und GOTO-Funktionen * @param displayParseTableGui */ public void setDisplayParseTableGui(boolean displayParseTableGui) { this.displayParseTableGui = displayParseTableGui; } /** * Schreibt die XML-Repräsentation des SyntaxTree in den übergebenen StringBuffer. * @param strBuf */ public void printParseTree(StringBuffer strBuf) { if (syntaxTree==null){ throw new IllegalStateException("printParseTree must not be called before parse."); } strBuf.append(syntaxTree.toString()); } /** * Parst ein Eingabeprogramm für eine Grammatik und gibt den erzeugten ISyntaxTree zurück. * @param lexer ILexer für das Eingabeprogramm * @param grammarPath Pfad zu einer Grammtikdatei in BNF * @return der resultierende Parsebaum * @throws #{@link LRParserException}, falls das Parsen fehlschlägt. */ @Override public ISyntaxTree parse(ILexer lexer, String grammarPath) { File grammarFile = new File(grammarPath); return parse(lexer, grammarFile); } /** * Parst ein Eingabeprogramm für eine Grammatik und gibt den erzeugten ISyntaxTree zurück. * @param lexer ILexer für das Eingabeprogramm * @param grammarFile Grammtikdatei in BNF * @return der resultierende Parsebaum * @throws #{@link LRParserException}, falls das Parsen fehlschlägt. */ public ISyntaxTree parse(ILexer lexer, File grammarFile) { Grammar grammar = null; // Grammatik einlesen try { GrammarReader grammarReader=new BNFGrammarReader(); grammar=grammarReader.readGrammar(grammarFile.getAbsolutePath()); } catch (BNFParsingErrorException e) { logger.log(Level.WARNING, "Failed to read grammar from file: "+grammarFile.getAbsolutePath(), e); throw new LRParserException("Failed to read grammar from file: "+grammarFile.getAbsolutePath(),e); } return parse(lexer, grammar); } /** * Parst ein Eingabeprogramm für eine Grammatik und gibt den erzeugten ISyntaxTree zurück. * @param lexer ILexer für das Eingabeprogramm * @param grammarReader #{@link java.io.Reader} für eine Grammtikdatei in BNF * @return der resultierende Parsebaum * @throws #{@link LRParserException}, falls das Parsen fehlschlägt. */ public ISyntaxTree parse(ILexer lexer, Reader grammarReader) { Grammar grammar = null; // Grammatik einlesen try { GrammarReader _grammarReader=new BNFGrammarReader(); grammar=_grammarReader.readGrammar(grammarReader); } catch (BNFParsingErrorException e) { logger.log(Level.WARNING, "Failed to read grammar from Reader.", e); throw new LRParserException("Failed to read grammar from Reader.",e); } return parse(lexer, grammar); } /** * Parst ein Eingabeprogramm für eine Grammatik und gibt den erzeugten ISyntaxTree zurück. * @param lexer ILexer für das Eingabeprogramm * @param grammar Grammtik * @return der resultierende Parsebaum * @throws #{@link LRParserException}, falls das Parsen fehlschlägt. */ private ISyntaxTree parse(ILexer lexer, Grammar grammar) { this.syntaxTree=null; // reset SyntaxTree LRParser parser=new LRParser(); parser.setDisplayParseTableGui(displayParseTableGui); parser.setReduceToAbstractSyntaxTree(reduceToAbstractSyntaxTree); parser.setRemoveEpsilonNodes(removeEpsilonNodes); this.syntaxTree= parser.parse(lexer, grammar); // SyntaxTree für printParseTree speichern return this.syntaxTree; } /** * * @return Beschreibung des Kommandozeilen-Interfaces. */ private static String usage(){ StringBuffer strBuf=new StringBuffer(); strBuf.append("NAME"); strBuf.append("\n\tLRParserMain - parses a source file using the LR-parse-algorithm."); strBuf.append("\nSYNOPSIS"); strBuf.append("\n\tLRParserMain [OPTIONS] <source_file>"); strBuf.append("\nOPTIONS"); strBuf.append("\n\t-v --version"); strBuf.append("\n\t\tPrint version and exit."); strBuf.append("\n\t-h --help"); strBuf.append("\n\t\tPrint usage and exit"); strBuf.append("\n\t-o --print-parse-tree <target_file>"); strBuf.append("\n\t\tPrint the resulting parse tree to <target_file>."); strBuf.append("\n\t-l --lexer bi|bii|a"); strBuf.append("\n\t\tUse lexer bi, bii or a. Defaults to "+DEFAULT_LEXER); strBuf.append("\n\t-t --token-definitions <token_definitions_file>"); strBuf.append("\n\t\tUse token defintions from <token_definitions_file>. Defaults to "+DEFAULT_TOKEN_DEFINITION_FILE); strBuf.append("\n\t-g --grammar <grammar_file>"); strBuf.append("\n\t\tUse grammar from <grammar_file>. Defaults to "+DEFAULT_GRAMMAR_FILE); strBuf.append("\n\t--reduce-to-abstract-syntax-tree"); strBuf.append("\n\t\tReduce the resulting parse tree by pulling up all single child nodes. Defaults to don't reduce"); strBuf.append("\n\t--dont-remove-epsilon-nodes"); strBuf.append("\n\t\tDon't remove epsilon nodes from the resulting parse tree. Defaults to remove epsilon nodes"); strBuf.append("\n\t--displayParseTable"); strBuf.append("\n\t\tOpen a swing gui displaying the Action and Goto table."); strBuf.append("\n\t--log-level-console OFF|SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST|ALL"); strBuf.append("\n\t\tSet the logging level for console output. Defaults to "+DEFAULT_LOG_LEVEL_CONSOLE); strBuf.append("\n\t--log-level-file OFF|SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST|ALL"); strBuf.append("\n\t\tSet the logging level for logfile output. Defaults to "+DEFAULT_LOG_LEVEL_FILE); strBuf.append("\n\t--log-file <log_file>"); strBuf.append("\n\t\tWrite logging output to <log_file>. Defaults to no logfile."); return strBuf.toString(); } /** * Ermöglicht die Verwendung des LRParsers über die Kommandozeile. <code> NAME LRParserMain - parses a source file using the LR-parse-algorithm. SYNOPSIS LRParserMain [OPTIONS] <source_file> OPTIONS -v --version Print version and exit. -h --help Print usage and exit -o --print-parse-tree <target_file> Print the resulting parse tree to <target_file>. -l --lexer bi|bii|a Use lexer bi, bii or a. Defaults to bi -t --token-definitions <token_definitions_file> Use token defintions from <token_definitions_file>. Defaults to input/de/fuberlin/bii/def/tokendefinition.rd -g --grammar <grammar_file> Use grammar from <grammar_file>. Defaults to input/de/fuberlin/projectci/non-ambigous.txt --reduce-to-abstract-syntax-tree Reduce the resulting parse tree by pulling up all single child nodes. Defaults to don't reduce --dont-remove-epsilon-nodes Don't remove epsilon nodes from the resulting parse tree. Defaults to remove epsilon nodes --displayParseTable Open a swing gui displaying the Action and Goto table. --log-level-console OFF|SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST|ALL Set the logging level for console output. Defaults to INFO --log-level-file OFF|SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST|ALL Set the logging level for logfile output. Defaults to ALL --log-file <log_file> Write logging output to <log_file>. Defaults to no logfile. </code> */ public static void main(String[] args) { if (args.length==0){ System.out.println(usage()); return; } CommandLine commandLine=new CommandLine(args); if (commandLine.getValueForOption(CommandLine.Option.DisplayVersion)!=null){ System.out.println(VERSION); return; } if (commandLine.getValueForOption(CommandLine.Option.DisplayHelp)!=null){ System.out.println(usage()); return; } if (commandLine.getArguments().size()==0){ System.err.println("Error: Missing argument <sourceFile>"); System.out.println(usage()); return; } if (commandLine.getArguments().size()>1){ System.err.println("Error: Ambigious argument <sourceFile>"); System.out.println(usage()); return; } File sourceFile= new File(commandLine.getArguments().get(0)); if (!sourceFile.exists()){ System.err.println("Error: <sourceFile> doesn't exists: "+commandLine.getArguments().get(0)); return; } File tokenDefinitionFile=null; if (commandLine.getValueForOption(CommandLine.Option.TokenDefinitions)!=null){ tokenDefinitionFile=new File((String) commandLine.getValueForOption(CommandLine.Option.TokenDefinitions)); } else{ tokenDefinitionFile=new File(DEFAULT_TOKEN_DEFINITION_FILE); } if (!tokenDefinitionFile.exists()){ System.err.println("Error: <token_definitions_file> doesn't exists: "+tokenDefinitionFile); return; } File grammarFile=null; if (commandLine.getValueForOption(CommandLine.Option.Grammar)!=null){ grammarFile=new File((String) commandLine.getValueForOption(CommandLine.Option.Grammar)); } else{ grammarFile=new File(DEFAULT_GRAMMAR_FILE); } if (!grammarFile.exists()){ System.err.println("Error: <grammar_file> doesn't exists: "+grammarFile); return; } initLogging(commandLine); String strLexer=(String) commandLine.getValueForOption(CommandLine.Option.Lexer); if (strLexer==null) strLexer=DEFAULT_LEXER; ILexer lexer=null; boolean rebuildDFA=false; if ("bi".equals(strLexer)){ lexer = new Lexergen(tokenDefinitionFile, sourceFile, BuilderType.indirectBuilder, CorrectionMode.PANIC_MODE, rebuildDFA); } else if ("bii".equals(strLexer)){ lexer = new Lexergen(tokenDefinitionFile, sourceFile, BuilderType.directBuilder, CorrectionMode.PANIC_MODE, rebuildDFA); } else if ("a".equals(strLexer)){ ICharStream stream = new FileCharStream(sourceFile.getAbsolutePath()); lexer = new Lexer(stream); } else{ System.err.println("Error: Unknown lexer "+strLexer); System.out.println(usage()); return; } LRParserMain parserMain=new LRParserMain(); if (commandLine.getValueForOption(CommandLine.Option.DisplayParseTable)!=null){ parserMain.setDisplayParseTableGui(true); } if (commandLine.getValueForOption(CommandLine.Option.ReduceToAbstractSyntaxTree)!=null){ parserMain.setReduceToAbstractSyntaxTree(true); } if (commandLine.getValueForOption(CommandLine.Option.DontRemoveEpsilonNodes)!=null){ parserMain.setRemoveEpsilonNodes(false); } ISyntaxTree syntaxTree=parserMain.parse(lexer, grammarFile); if (syntaxTree==null){ System.err.println("Error: Failed to parse "+sourceFile); return; } if (commandLine.getValueForOption(CommandLine.Option.PrintParseTree)!=null){ String outputFilePath=(String) commandLine.getValueForOption(CommandLine.Option.PrintParseTree); try { BufferedWriter out = new BufferedWriter(new FileWriter(outputFilePath)); out.write(syntaxTree.toString()); out.close(); } catch (IOException e) { System.err.println("Error: Failed to write parsetree to file "+outputFilePath); e.printStackTrace(); return; } } System.out.println("OK"); } /** Initialisiert das Logging anhand der Kommandozeilen-Argumente */ private static void initLogging(CommandLine commandLine){ Level logLevelConsole=DEFAULT_LOG_LEVEL_CONSOLE; Level logLevelFile=DEFAULT_LOG_LEVEL_FILE; File logFile=null; String strLogLevelConsole=(String) commandLine.getValueForOption(CommandLine.Option.LogLevelConsole); if (strLogLevelConsole!=null){ try { logLevelConsole=Level.parse(strLogLevelConsole); } catch (IllegalArgumentException e) { System.err.println("Error: Invalid log level for console: "+strLogLevelConsole+". Use default: "+logLevelConsole); } } String strLogLevelFile=(String) commandLine.getValueForOption(CommandLine.Option.LogLevelFile); if (strLogLevelFile!=null){ try { logLevelFile=Level.parse(strLogLevelFile); } catch (IllegalArgumentException e) { System.err.println("Error: Invalid log level for logfile: "+strLogLevelFile+". Use default: "+logLevelFile); } } String logFilePath=(String) commandLine.getValueForOption(CommandLine.Option.LogFile); if (logFilePath!=null){ logFile=new File(logFilePath); // if (!logFile.canWrite()){ // System.err.println("Cannot write to log file configuted by param "+PARAM_LOG_FILE +": "+logFilePath); // logFile=null; // } } LogFactory.init(logLevelConsole, logLevelFile, logFile!=null?logFile.getAbsolutePath():null); } /** * Hilfsklasse zum Parsen der Kommandozeilen-Argumente. */ private static class CommandLine{ private Map<Option,Object> option2Value= new HashMap<Option, Object>(); private List<String> arguments=new ArrayList<String>(); public CommandLine(String[] args){ for (int i = 0; i < args.length; i++) { String arg = args[i]; if (Option.pattern2Option.containsKey(arg)){ Option option=Option.pattern2Option.get(arg); if (option.defaultValue!=null){ option2Value.put(option, option.defaultValue); } else{ if (i<args.length-1){ // nicht das letzte Argument String value=args[++i]; if ((value.startsWith("-") || value.startsWith("--"))){ throw new CommandLineParseException("Illegal value ('"+value+"' for option "+option); } option2Value.put(option,value); } else{ throw new CommandLineParseException("Missing value for option "+option); } } } else{ arguments.add(arg); } } } public Object getValueForOption(Option option){ return option2Value.get(option); } public List<String> getArguments(){ return arguments; } /** * Beschreibung der erlaubten Kommandozeilen-Optionen * */ private static enum Option{ DisplayVersion("--version", "-v", true), DisplayHelp("--help", "-h", true), PrintParseTree("--print-parse-tree","-o", null), Lexer("--lexer", "-l", null), TokenDefinitions("--token-definitions", "-t", null), Grammar("--grammar", "-g" , null), ReduceToAbstractSyntaxTree("--reduce-to-abstract-syntax-tree", null, true), DontRemoveEpsilonNodes("--dont-remove-epsilon-nodes", null, true), DisplayParseTable("--displayParseTable", null, true), LogLevelConsole("--log-level-console", null, null), LogLevelFile("--log-level-file", null, null), LogFile("--log-file", null, null); private final String pattern; private final String aliasPattern; private final Object defaultValue; /** * Erzeugt eine neue Kommandozeilen-Option * @param pattern Hauptpattern für die Kommandozeilen-Option * @param alias Alternativpattern für die Kommandozeilen-Option * @param defaultValue Default-Wert für Switches (Null, wenn zur Option noch ein Wert übergeben werden muss). */ private Option(String pattern, String alias, Object defaultValue){ this.pattern=pattern; this.aliasPattern=alias; this.defaultValue=defaultValue; } private static Map<String,Option> pattern2Option= new HashMap<String, Option>(); static{ for(Option anOption : EnumSet.allOf(Option.class)){ pattern2Option.put(anOption.pattern, anOption); if (anOption.aliasPattern!=null){ pattern2Option.put(anOption.aliasPattern, anOption); } } } } /** Signalisiert einen Fehler in den Kommandozeilen-Argumenten */ @SuppressWarnings("serial") private static class CommandLineParseException extends RuntimeException{ public CommandLineParseException(String message) { super(message); } } } }