{- BNF Converter: Java Pretty Printer generator Copyright (C) 2004 Author: Michael Pellauer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -} {- ************************************************************** BNF Converter Module Description : This module generates the Java Pretty Printer class. In addition, since there's no good way to display a class heirarchy (toString() doesn't count) in Java, it generates a method that displays the Abstract Syntax in a way similar to Haskell. This uses Appel's method and may serve as a useful example to those who wish to use it. Author : Michael Pellauer (pellauer@cs.chalmers.se) License : GPL (GNU General Public License) Created : 24 April, 2003 Modified : 2 September, 2003 Added string buffer for efficiency (Michael, August 03) ************************************************************** -} module CFtoJavaPrinter ( cf2JavaPrinter ) where import CF import NamedVariables import Utils ( (+++) ) import Data.List import Data.Char ( toLower ) --Produces the PrettyPrinter class. --It will generate two methods "print" and "show" --print is the actual pretty printer for linearization. --show produces a Haskell-style syntax that can be extremely useful --especially for testing parser correctness. cf2JavaPrinter :: String -> String -> CF -> String cf2JavaPrinter packageBase packageAbsyn cf = unlines [ header, prEntryPoints packageAbsyn cf, unlines (map (prData packageAbsyn) groups), unlines (map (shData packageAbsyn) groups), footer ] where groups = (fixCoercions (ruleGroupsInternals cf)) header = unlines [ "package" +++ packageBase ++ ";", "import" +++ packageAbsyn ++ ".*;", "", "public class PrettyPrinter", "{", " //For certain applications increasing the initial size of the buffer may improve performance.", " private static final int INITIAL_BUFFER_SIZE = 128;", " //You may wish to change the parentheses used in precedence.", " private static final String _L_PAREN = new String(\"(\");", " private static final String _R_PAREN = new String(\")\");", prRender ] footer = unlines [ --later only include used categories " private static void pp(Integer n, int _i_) { buf_.append(n); buf_.append(\" \"); }", " private static void pp(Double d, int _i_) { buf_.append(d); buf_.append(\" \"); }", " private static void pp(String s, int _i_) { buf_.append(s); buf_.append(\" \"); }", " private static void pp(Character c, int _i_) { buf_.append(\"'\" + c.toString() + \"'\"); buf_.append(\" \"); }", " private static void sh(Integer n) { buf_.append(n); }", " private static void sh(Double d) { buf_.append(d); }", " private static void sh(Character c) { buf_.append(c); }", " private static void sh(String s) { printQuoted(s); }", " private static void printQuoted(String s) { buf_.append(\"\\\"\" + s + \"\\\"\"); }", " private static void indent()", " {", " int n = _n_;", " while (n > 0)", " {", " buf_.append(\" \");", " n--;", " }", " }", " private static void backup()", " {", " String s = buf_.toString();", -- peteg: java 1.1.8 compatibility. "", " if (s.substring(buf_.length() - 1, buf_.length()).equals(\" \")) {", " buf_.setCharAt(buf_.length() - 1, '\\\"');", " buf_.setLength(buf_.length() - 1);", " }", " }", " private static int _n_ = 0;", " private static StringBuffer buf_ = new StringBuffer(INITIAL_BUFFER_SIZE);", "}" ] --An extremely simple renderer for terminals. prRender :: String prRender = unlines [ " //You may wish to change render", " private static void render(String s)", " {", " if (s.equals(\"{\"))", " {", " buf_.append(\"\\n\");", " indent();", " buf_.append(s);", " _n_ = _n_ + 2;", " buf_.append(\"\\n\");", " indent();", " }", " else if (s.equals(\"(\") || s.equals(\"[\"))", " buf_.append(s);", " else if (s.equals(\")\") || s.equals(\"]\"))", " {", " backup();", " buf_.append(s);", " buf_.append(\" \");", " }", " else if (s.equals(\"}\"))", " {", " _n_ = _n_ - 2;", " backup();", " backup();", " buf_.append(s);", " buf_.append(\"\\n\");", " indent();", " }", " else if (s.equals(\",\"))", " {", " backup();", " buf_.append(s);", " buf_.append(\" \");", " }", " else if (s.equals(\";\"))", " {", " backup();", " buf_.append(s);", " buf_.append(\"\\n\");", " indent();", " }", " else if (s.equals(\"\")) return;", " else", " {", " buf_.append(s);", " buf_.append(\" \");", " }", " }" ] prEntryPoints :: String -> CF -> String prEntryPoints packageAbsyn cf = msg ++ concat (map prEntryPoint (allEntryPoints cf)) ++ msg2 where msg = " // print and show methods are defined for each Entry Point type.\n" msg2 = " /*** You shouldn't need to change anything beyond this point. ***/\n" prEntryPoint cat | (normCat cat) == cat = unlines [ " public static String print(" ++ packageAbsyn ++ "." ++ cat' ++ " foo)", " {", " pp(foo, 0);", " String temp = buf_.toString();", " buf_ = new StringBuffer(INITIAL_BUFFER_SIZE);", " return temp;", " }", " public static String show(" ++ packageAbsyn ++ "." ++ cat' ++ " foo)", " {", " sh(foo);", " String temp = buf_.toString();", " buf_ = new StringBuffer(INITIAL_BUFFER_SIZE);", " return temp;", " }" ] where cat' = identCat cat prEntryPoint _ = "" prData :: String -> (Cat, [Rule]) -> String prData packageAbsyn (cat, rules) = if isList cat then unlines [ " private static void pp(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo, int _i_)", " {", (prList cat rules) ++ " }" ] else unlines --not a list [ " private static void pp(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo, int _i_)", " {", (concat (map (prRule packageAbsyn) rules)) ++ " }" ] prRule :: String -> Rule -> String prRule packageAbsyn r@(Rule fun _c cats) | not (isCoercion fun) && not (isDefinedRule fun) = concat [ " if (foo instanceof" +++ packageAbsyn ++ "." ++ fun ++ ")\n", " {\n", " " ++ packageAbsyn ++ "." ++ fun +++ fnm +++ "= (" ++ packageAbsyn ++ "." ++ fun ++ ") foo;\n", lparen, cats', rparen, " }\n" ] where p = precRule r (lparen, rparen) = (" if (_i_ > " ++ (show p) ++ ") render(_L_PAREN);\n", " if (_i_ > " ++ (show p) ++ ") render(_R_PAREN);\n") cats' = case cats of [] -> "" _ -> concatMap (prCat fnm) (zip (fixOnes (numVars [] cats)) (map getPrec cats)) fnm = '_' : map toLower fun getPrec (Right {}) = 0 getPrec (Left c) = precCat c prRule _nm _ = "" prList :: Cat -> [Rule] -> String prList c rules = unlines [ " while (foo != null)", " {", " if (foo." ++ c'' ++ "_ == null)", " {", " pp(foo." ++ c' ++ "_, 0);", optsep, " }", " else", " {", " pp(foo." ++ c' ++ "_, 0);", " render(\"" ++ (escapeChars sep) ++ "\");", " }", " foo = foo." ++ c'' ++ "_;", " }" ] where c' = map toLower (identCat (normCatOfList c)) c'' = map toLower (identCat c) sep = getCons rules optsep = if hasOneFunc rules then "" else (" render(\"" ++ (escapeChars sep) ++ "\");") prCat fnm (c, p) = case c of Right t -> " render(\"" ++ escapeChars t ++ "\");\n" Left nt | "string" `isPrefixOf` nt -> " printQuoted(" ++ fnm ++ "." ++ nt ++ ");\n" | isInternalVar nt -> "" | otherwise -> " pp(" ++ fnm ++ "." ++ nt ++ ", " ++ show p ++ ");\n" --The following methods generate the Show function. shData :: String -> (Cat, [Rule]) -> String shData packageAbsyn (cat, rules) = if isList cat then unlines [ " private static void sh(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo)", " {", (shList cat rules) ++ " }" ] else unlines [ " private static void sh(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo)", " {", (concat (map (shRule packageAbsyn) rules)) ++ " }" ] shRule :: String -> Rule -> String shRule packageAbsyn (Rule fun _c cats) | not (isCoercion fun) && not (isDefinedRule fun) = unlines [ " if (foo instanceof" +++ packageAbsyn ++ "." ++ fun ++ ")", " {", " " ++ packageAbsyn ++ "." ++ fun +++ fnm +++ "= (" ++ packageAbsyn ++ "." ++ fun ++ ") foo;", members ++ " }" ] where members = concat [ lparen, " render(\"" ++ (escapeChars fun) ++ "\");\n", cats', rparen ] cats' = if allTerms cats then "" else (concat (map (shCat fnm) (fixOnes (numVars [] cats)))) (lparen, rparen) = if allTerms cats then ("","") else (" render(\"(\");\n"," render(\")\");\n") allTerms [] = True allTerms ((Left {}):_) = False allTerms (_:zs) = allTerms zs fnm = '_' : map toLower fun shRule _nm _ = "" shList :: Cat -> [Rule] -> String shList c _rules = unlines [ " while (foo != null)", " {", " if (foo." ++ c'' ++ "_ == null)", " {", " sh(foo." ++ c' ++ "_);", " }", " else", " {", " sh(foo." ++ c' ++ "_);", " render(\",\");", " }", " foo = foo." ++ c'' ++ "_;", " }" ] where c' = map toLower (identCat (normCatOfList c)) c'' = map toLower (identCat c) shCat fnm c = case c of Right {} -> "" Left nt | "list" `isPrefixOf` nt -> unlines [" render(\"[\");", " sh(" ++ fnm ++ "." ++ nt ++ ");", " render(\"]\");"] | isInternalVar nt -> "" | otherwise -> " sh(" ++ fnm ++ "." ++ nt ++ ");\n" --Helper function that escapes characters in strings escapeChars :: String -> String escapeChars [] = [] escapeChars ('\\':xs) = '\\' : ('\\' : (escapeChars xs)) escapeChars ('\"':xs) = '\\' : ('\"' : (escapeChars xs)) escapeChars (x:xs) = x : (escapeChars xs) isInternalVar x = x == internalCat ++ "_"