001: /*
002: * Grammatica.java
003: *
004: * This work is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published
006: * by the Free Software Foundation; either version 2 of the License,
007: * or (at your option) any later version.
008: *
009: * This work is distributed in the hope that it will be useful, but
010: * WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
017: * USA
018: *
019: * As a special exception, the copyright holders of this library give
020: * you permission to link this library with independent modules to
021: * produce an executable, regardless of the license terms of these
022: * independent modules, and to copy and distribute the resulting
023: * executable under terms of your choice, provided that you also meet,
024: * for each linked independent module, the terms and conditions of the
025: * license of that module. An independent module is a module which is
026: * not derived from or based on this library. If you modify this
027: * library, you may extend this exception to your version of the
028: * library, but you are not obligated to do so. If you do not wish to
029: * do so, delete this exception statement from your version.
030: *
031: * Copyright (c) 2003 Per Cederberg. All rights reserved.
032: */
033:
034: package net.percederberg.grammatica;
035:
036: import java.io.BufferedReader;
037: import java.io.File;
038: import java.io.FileNotFoundException;
039: import java.io.FileReader;
040: import java.io.IOException;
041:
042: import net.percederberg.grammatica.output.CSharpParserGenerator;
043: import net.percederberg.grammatica.output.JavaParserGenerator;
044: import net.percederberg.grammatica.parser.Analyzer;
045: import net.percederberg.grammatica.parser.Node;
046: import net.percederberg.grammatica.parser.ParseException;
047: import net.percederberg.grammatica.parser.Parser;
048: import net.percederberg.grammatica.parser.ParserCreationException;
049: import net.percederberg.grammatica.parser.ParserLogException;
050: import net.percederberg.grammatica.parser.Token;
051: import net.percederberg.grammatica.parser.Tokenizer;
052:
053: /**
054: * The main application. This class provides the command-line
055: * interface for invoking the application. See separate documentation
056: * for information on usage and command-line parameters.
057: *
058: * @author Per Cederberg, <per at percederberg dot net>
059: * @version 1.2
060: */
061: public class Grammatica extends Object {
062:
063: /**
064: * The command-line help output.
065: */
066: private static final String COMMAND_HELP = "Generates source code for a C# or Java parser from a grammar\n"
067: + "file. This program comes with ABSOLUTELY NO WARRANTY; for\n"
068: + "details see the LICENSE.txt file.\n"
069: + "\n"
070: + "Syntax: Grammatica <grammarfile> <action> [<options>]\n"
071: + "\n"
072: + "Actions:\n"
073: + " --debug\n"
074: + " Debugs the grammar by validating it and printing the\n"
075: + " internal representation.\n"
076: + " --tokenize <file>\n"
077: + " Debugs the grammar by using it to tokenize the specified\n"
078: + " file. No code has to be generated for this.\n"
079: + " --parse <file>\n"
080: + " Debugs the grammar by using it to parse the specified\n"
081: + " file. No code has to be generated for this.\n"
082: + " --profile <file>\n"
083: + " Profiles the grammar by using it to parse the specified\n"
084: + " file and printing a statistic summary.\n"
085: + " --csoutput <dir>\n"
086: + " Creates a C# parser for the grammar (in source code).\n"
087: + " The specified directory will be used as output directory\n"
088: + " for the source code files.\n"
089: + " --javaoutput <dir>\n"
090: + " Creates a Java parser for the grammar (in source code).\n"
091: + " The specified directory will be used as the base output\n"
092: + " directory for the source code files.\n"
093: + "\n"
094: + "C# Output Options:\n"
095: + " --csnamespace <package>\n"
096: + " Sets the C# namespace to use in generated source code\n"
097: + " files. By default no namespace declaration is included.\n"
098: + " --csclassname <name>\n"
099: + " Sets the C# class name prefix to use in generated source\n"
100: + " code files. By default the grammar file name is used.\n"
101: + " --cspublic\n"
102: + " Sets public access for all C# types generated. By default\n"
103: + " type access is internal.\n"
104: + "\n"
105: + "Java Output Options:\n"
106: + " --javapackage <package>\n"
107: + " Sets the Java package to use in generated source code\n"
108: + " files. By default no package declaration is included.\n"
109: + " --javaclassname <name>\n"
110: + " Sets the Java class name prefix to use in generated source\n"
111: + " code files. By default the grammar file name is used.\n"
112: + " --javapublic\n"
113: + " Sets public access for all Java types. By default type\n"
114: + " access is package local.";
115:
116: /**
117: * The internal error message.
118: */
119: private static final String INTERNAL_ERROR = "INTERNAL ERROR: An internal error in Grammatica has been found.\n"
120: + " Please report this error to the maintainers (see the web\n"
121: + " site for instructions). Be sure to include the Grammatica\n"
122: + " version number, as well as the information below:\n";
123:
124: /**
125: * The application entry point.
126: *
127: * @param args the command-line parameters
128: */
129: public static void main(String[] args) {
130: Grammar grammar = null;
131:
132: // Parse command-line arguments
133: if (args.length == 1 && args[0].equals("--help")) {
134: printHelp(null);
135: System.exit(1);
136: }
137: if (args.length < 2) {
138: printHelp("Missing grammar file and/or action");
139: System.exit(1);
140: }
141:
142: // Read grammar file
143: try {
144: grammar = new Grammar(new File(args[0]));
145: } catch (FileNotFoundException e) {
146: printError(args[0], e);
147: System.exit(1);
148: } catch (ParserLogException e) {
149: printError(args[0], e);
150: System.exit(1);
151: } catch (GrammarException e) {
152: printError(e);
153: System.exit(1);
154: } catch (SecurityException e) {
155: throw e;
156: } catch (RuntimeException e) {
157: printInternalError(e);
158: System.exit(2);
159: }
160:
161: // Check action parameter
162: try {
163: if (args[1].equals("--debug")) {
164: debug(grammar);
165: } else if (args.length < 3) {
166: printHelp("missing action file parameter");
167: System.exit(1);
168: } else if (args[1].equals("--tokenize")) {
169: tokenize(grammar, new File(args[2]));
170: } else if (args[1].equals("--parse")) {
171: parse(grammar, new File(args[2]));
172: } else if (args[1].equals("--profile")) {
173: profile(grammar, new File(args[2]));
174: } else if (args[1].equals("--javaoutput")) {
175: writeJavaCode(args, grammar);
176: } else if (args[1].equals("--csoutput")) {
177: writeCSharpCode(args, grammar);
178: } else {
179: printHelp("unrecognized option: " + args[1]);
180: System.exit(1);
181: }
182: } catch (SecurityException e) {
183: throw e;
184: } catch (RuntimeException e) {
185: printInternalError(e);
186: System.exit(2);
187: }
188: }
189:
190: /**
191: * Prints command-line help information.
192: *
193: * @param error an optional error message, or null
194: */
195: private static void printHelp(String error) {
196: System.err.println(COMMAND_HELP);
197: System.err.println();
198: if (error != null) {
199: System.err.print("Error: ");
200: System.err.println(error);
201: System.err.println();
202: }
203: }
204:
205: /**
206: * Prints a general error message.
207: *
208: * @param e the detailed exception
209: */
210: private static void printError(Exception e) {
211: StringBuffer buffer = new StringBuffer();
212:
213: buffer.append("Error: ");
214: buffer.append(e.getMessage());
215: System.err.println(buffer.toString());
216: }
217:
218: /**
219: * Prints a file not found error message.
220: *
221: * @param file the file name not found
222: * @param e the detailed exception
223: */
224: private static void printError(String file, FileNotFoundException e) {
225: StringBuffer buffer = new StringBuffer();
226:
227: buffer.append("Error: couldn't open file:");
228: buffer.append("\n ");
229: buffer.append(file);
230: System.err.println(buffer.toString());
231: }
232:
233: /**
234: * Prints a parse error message.
235: *
236: * @param file the input file name
237: * @param e the detailed exception
238: */
239: private static void printError(String file, ParseException e) {
240: StringBuffer buffer = new StringBuffer();
241: String line;
242:
243: // Handle normal parse error
244: buffer.append("Error: in ");
245: buffer.append(file);
246: if (e.getLine() > 0) {
247: buffer.append(": line ");
248: buffer.append(e.getLine());
249: }
250: buffer.append(":\n");
251: buffer.append(linebreakString(e.getErrorMessage(), " ", 70));
252: line = readLines(file, e.getLine(), e.getLine());
253: if (line != null) {
254: buffer.append("\n\n");
255: buffer.append(line);
256: for (int i = 1; i < e.getColumn(); i++) {
257: if (line.charAt(i - 1) == '\t') {
258: buffer.append("\t");
259: } else {
260: buffer.append(" ");
261: }
262: }
263: buffer.append("^");
264: }
265: System.err.println(buffer.toString());
266: }
267:
268: /**
269: * Prints a list of parse error messages.
270: *
271: * @param file the input file name
272: * @param e the parser log exception
273: */
274: private static void printError(String file, ParserLogException e) {
275: for (int i = 0; i < e.getErrorCount(); i++) {
276: printError(file, e.getError(i));
277: }
278: }
279:
280: /**
281: * Prints a grammar error message.
282: *
283: * @param e the detailed exception
284: */
285: private static void printError(GrammarException e) {
286: StringBuffer buffer = new StringBuffer();
287: String lines;
288:
289: buffer.append("Error: in ");
290: buffer.append(e.getFile());
291: if (e.getStartLine() > 0) {
292: if (e.getStartLine() == e.getEndLine()) {
293: buffer.append(": line ");
294: buffer.append(e.getStartLine());
295: } else {
296: buffer.append(": lines ");
297: buffer.append(e.getStartLine());
298: buffer.append("-");
299: buffer.append(e.getEndLine());
300: }
301: }
302: buffer.append(":\n");
303: buffer.append(linebreakString(e.getErrorMessage(), " ", 70));
304: lines = readLines(e.getFile(), e.getStartLine(), e.getEndLine());
305: if (lines != null) {
306: buffer.append("\n\n");
307: buffer.append(lines);
308: }
309: System.err.println(buffer.toString());
310: }
311:
312: /**
313: * Prints an internal error message. This type of error should
314: * only be reported when run-time exceptions occur, such as null
315: * pointer and the likes. All these error should be reported as
316: * bugs to the program maintainers.
317: *
318: * @param e the exception to be reported
319: */
320: private static void printInternalError(Exception e) {
321: System.err.println(INTERNAL_ERROR);
322: e.printStackTrace();
323: }
324:
325: /**
326: * Breaks a string into multiple lines. This method will also add
327: * a prefix to each line in the resulting string. The prefix
328: * length will be taken into account when breaking the line. Line
329: * breaks will only be inserted as replacements for space
330: * characters.
331: *
332: * @param str the string to line break
333: * @param prefix the prefix to add to each line
334: * @param length the maximum line length
335: *
336: * @return the new formatted string
337: */
338: private static String linebreakString(String str, String prefix,
339: int length) {
340:
341: StringBuffer buffer = new StringBuffer();
342: int pos;
343:
344: while (str.length() + prefix.length() > length) {
345: pos = str.lastIndexOf(' ', length - prefix.length());
346: if (pos < 0) {
347: pos = str.indexOf(' ');
348: if (pos < 0) {
349: break;
350: }
351: }
352: buffer.append(prefix);
353: buffer.append(str.substring(0, pos));
354: str = str.substring(pos + 1);
355: buffer.append("\n");
356: }
357: buffer.append(prefix);
358: buffer.append(str);
359: return buffer.toString();
360: }
361:
362: /**
363: * Reads a number of lines from a file. In the file couldn't be
364: * opened or read correctly, null will be returned.
365: *
366: * @param file the name of the file to read
367: * @param start the first line number to read, from one (1)
368: * @param end the last line number to read, from one (1)
369: *
370: * @return the lines read including newline characters
371: */
372: private static String readLines(String file, int start, int end) {
373: BufferedReader input;
374: StringBuffer buffer = new StringBuffer();
375: String str;
376:
377: // Check invalid line number
378: if (start < 1 || end < start) {
379: return null;
380: }
381:
382: // Read line from file
383: try {
384: input = new BufferedReader(new FileReader(file));
385: for (int i = 0; i < end; i++) {
386: str = input.readLine();
387: if (str == null) {
388: input.close();
389: return null;
390: } else if (start <= i + 1) {
391: buffer.append(str);
392: buffer.append("\n");
393: }
394: }
395: input.close();
396: } catch (IOException e) {
397: return null;
398: }
399:
400: return buffer.toString();
401: }
402:
403: /**
404: * Debugs a grammar by printing the internal representation.
405: *
406: * @param grammar the grammar to use
407: */
408: private static void debug(Grammar grammar) {
409: Tokenizer tokenizer = null;
410: Parser parser = null;
411:
412: // Create tokenizer and parser
413: try {
414: tokenizer = grammar.createTokenizer(null);
415: parser = grammar.createParser(tokenizer);
416: } catch (GrammarException e) {
417: printInternalError(e);
418: System.exit(2);
419: }
420:
421: // Print tokenizer and parser
422: System.out
423: .println("Contents of " + grammar.getFileName() + ":");
424: System.out.println();
425: System.out.println("Token Declarations:");
426: System.out.println("-------------------");
427: System.out.print(tokenizer);
428: System.out.println("Production Declarations:");
429: System.out.println("------------------------");
430: System.out.print(parser);
431: }
432:
433: /**
434: * Tokenizes the specified file with the token patterns from the
435: * grammar.
436: *
437: * @param grammar the grammar to use
438: * @param file the file to parse
439: */
440: private static void tokenize(Grammar grammar, File file) {
441: Tokenizer tokenizer;
442: Token token;
443:
444: try {
445: tokenizer = grammar.createTokenizer(new FileReader(file));
446: System.out.println("Tokens from " + file + ":");
447: while ((token = tokenizer.next()) != null) {
448: System.out.println(token);
449: }
450: } catch (FileNotFoundException e) {
451: printError(file.toString(), e);
452: System.exit(1);
453: } catch (GrammarException e) {
454: printInternalError(e);
455: System.exit(2);
456: } catch (ParseException e) {
457: printError(file.toString(), e);
458: System.exit(1);
459: }
460: }
461:
462: /**
463: * Parses the specified file with the grammar.
464: *
465: * @param grammar the grammar to use
466: * @param file the file to parse
467: */
468: private static void parse(Grammar grammar, File file) {
469: Tokenizer tokenizer;
470: Analyzer analyzer;
471: Parser parser;
472:
473: try {
474: tokenizer = grammar.createTokenizer(new FileReader(file));
475: analyzer = new TreePrinter(System.out);
476: parser = grammar.createParser(tokenizer, analyzer);
477: System.out.println("Parse tree from " + file + ":");
478: parser.parse();
479: } catch (FileNotFoundException e) {
480: printError(file.toString(), e);
481: System.exit(1);
482: } catch (GrammarException e) {
483: printInternalError(e);
484: System.exit(2);
485: } catch (ParserCreationException e) {
486: printInternalError(e);
487: System.exit(2);
488: } catch (ParserLogException e) {
489: printError(file.toString(), e);
490: System.exit(1);
491: }
492: }
493:
494: /**
495: * Parses the specified file with the grammar and prints
496: * profiling information.
497: *
498: * @param grammar the grammar to use
499: * @param file the file to parse
500: */
501: private static void profile(Grammar grammar, File file) {
502: Tokenizer tokenizer;
503: Parser parser;
504: Node node;
505: long time;
506: int counter;
507:
508: // Profile tokenizer
509: try {
510: tokenizer = grammar.createTokenizer(new FileReader(file));
511: System.out.println("Tokenizing " + file);
512: time = System.currentTimeMillis();
513: counter = 0;
514: while (tokenizer.next() != null) {
515: counter++;
516: }
517: time = System.currentTimeMillis() - time;
518: System.out
519: .println(" Time elapsed: " + time + " millisec");
520: System.out.println(" Tokens found: " + counter);
521: System.out.println(" Average speed: " + (counter / time)
522: + " tokens/millisec");
523: System.out.println();
524: } catch (FileNotFoundException e) {
525: printError(file.toString(), e);
526: System.exit(1);
527: } catch (GrammarException e) {
528: printInternalError(e);
529: System.exit(2);
530: } catch (ParseException e) {
531: printError(file.toString(), e);
532: System.exit(1);
533: }
534:
535: // Profile parser
536: try {
537: tokenizer = grammar.createTokenizer(new FileReader(file));
538: parser = grammar.createParser(tokenizer);
539: System.out.println("Parsing " + file);
540: time = System.currentTimeMillis();
541: node = parser.parse();
542: time = System.currentTimeMillis() - time;
543: counter = 1 + node.getDescendantCount();
544: System.out
545: .println(" Time elapsed: " + time + " millisec");
546: System.out.println(" Nodes found: " + counter);
547: System.out.println(" Average speed: " + (counter / time)
548: + " nodes/millisec");
549: System.out.println();
550: } catch (FileNotFoundException e) {
551: printError(file.toString(), e);
552: System.exit(1);
553: } catch (GrammarException e) {
554: printInternalError(e);
555: System.exit(2);
556: } catch (ParserCreationException e) {
557: printInternalError(e);
558: System.exit(2);
559: } catch (ParserLogException e) {
560: printError(file.toString(), e);
561: System.exit(1);
562: }
563: }
564:
565: /**
566: * Parses the command-line arguments and generates the Java source
567: * code for a parser.
568: *
569: * @param args the command-line arguments
570: * @param grammar the grammar to use
571: */
572: private static void writeJavaCode(String[] args, Grammar grammar) {
573: JavaParserGenerator gen = new JavaParserGenerator(grammar);
574:
575: // Read command-line arguments
576: for (int i = 1; i < args.length; i++) {
577: if (args[i].equals("--javaoutput")) {
578: gen.setBaseDir(new File(args[++i]));
579: } else if (args[i].equals("--javapackage")) {
580: gen.setBasePackage(args[++i]);
581: } else if (args[i].equals("--javaclassname")) {
582: gen.setBaseName(args[++i]);
583: } else if (args[i].equals("--javapublic")) {
584: gen.setPublicAccess(true);
585: } else {
586: printHelp("unrecognized option: " + args[i]);
587: System.exit(1);
588: }
589: }
590:
591: // Write parser source code
592: try {
593: System.out.println("Writing Java parser source code...");
594: gen.write();
595: System.out.println("Done.");
596: } catch (IOException e) {
597: printError(e);
598: System.exit(1);
599: }
600: }
601:
602: /**
603: * Parses the command-line arguments and generates the C# source
604: * code for a parser.
605: *
606: * @param args the command-line arguments
607: * @param grammar the grammar to use
608: */
609: private static void writeCSharpCode(String[] args, Grammar grammar) {
610: CSharpParserGenerator gen = new CSharpParserGenerator(grammar);
611:
612: // Read command-line arguments
613: for (int i = 1; i < args.length; i++) {
614: if (args[i].equals("--csoutput")) {
615: gen.setBaseDir(new File(args[++i]));
616: } else if (args[i].equals("--csnamespace")) {
617: gen.setNamespace(args[++i]);
618: } else if (args[i].equals("--csclassname")) {
619: gen.setBaseName(args[++i]);
620: } else if (args[i].equals("--cspublic")) {
621: gen.setPublicAccess(true);
622: } else {
623: printHelp("unrecognized option: " + args[i]);
624: System.exit(1);
625: }
626: }
627:
628: // Write parser source code
629: try {
630: System.out.println("Writing C# parser source code...");
631: gen.write();
632: System.out.println("Done.");
633: } catch (IOException e) {
634: printError(e);
635: System.exit(1);
636: }
637: }
638: }
|