{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- BNF Converter: Java Pretty Printer generator Copyright (C) 2004 Author: Michael Pellauer, Bjorn Bringert 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), Bjorn Bringert (bringert@cs.chalmers.se) Created : 24 April, 2003 Modified : 9 Aug, 2004 Added string buffer for efficiency (Michael, August 03) -} module BNFC.Backend.Java.CFtoJavaPrinter15 ( cf2JavaPrinter ) where import Prelude hiding ((<>)) import Data.Bifunctor ( second ) import Data.Char ( toLower, isSpace ) import Data.Either ( lefts ) import Data.List ( intersperse ) import BNFC.CF import BNFC.PrettyPrint import BNFC.Utils ( (+++), for, unless, unlessNull, uniqOn ) import BNFC.Backend.Common ( switchByPrecedence ) import BNFC.Backend.Common.NamedVariables import BNFC.Backend.Java.CFtoJavaAbs15 --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 user) groups), unlines (map (shData packageAbsyn user) groups), footer ] where user = [n | (n,_) <- tokenPragmas cf] groups = fixCoercions (ruleGroupsInternals cf) header = unlines [ "package" +++ packageBase ++ ";", "", "public class PrettyPrinter", "{", " //For certain applications increasing the initial size of the buffer may improve performance.", " private static final int INITIAL_BUFFER_SIZE = 128;", " private static final int INDENT_WIDTH = 2;", " //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(String.format(java.util.Locale.ROOT, \"%.15g \", d)); }", " 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) { render(n.toString()); }", " private static void sh(Double d) { render(String.format(java.util.Locale.ROOT, \"%.15g\", d)); }", " private static void sh(Character c) { render(\"'\" + c.toString() + \"'\"); }", " private static void sh(String s) { printQuoted(s); }", "", " private static void printQuoted(String s) { render(\"\\\"\" + s + \"\\\"\"); }", "", " private static void indent()", " {", " int n = _n_;", " while (n > 0)", " {", " buf_.append(\' \');", " n--;", " }", " }", "", " private static void backup()", " {", " int prev = buf_.length() - 1;", " if (prev >= 0 && buf_.charAt(prev) == ' ')", " buf_.setLength(prev);", " }", "", " private static void trim()", " {", " // Trim initial spaces", " int end = 0;", " int len = buf_.length();", " while (end < len && buf_.charAt(end) == ' ')", " end++; ", " buf_.delete(0, end);", " // Trim trailing spaces", " removeTrailingSpaces();", " }", "", " private static void removeTrailingSpaces()", " {", " int end = buf_.length();", " while (end > 0 && buf_.charAt(end-1) == ' ')", " end--;", " buf_.setLength(end);", " }", "", " private static void removeTrailingWhitespace()", " {", " int end = buf_.length();", " while (end > 0 && (buf_.charAt(end-1) == ' ' || buf_.charAt(end-1) == '\\n'))", " end--;", " buf_.setLength(end);", " }", "", " private static void onEmptyLine()", " {", " removeTrailingSpaces();", " int len = buf_.length();", " if (len > 0 && buf_.charAt(len-1) != '\\n') buf_.append(\"\\n\");", " indent();", " }", "", " private static int _n_ = 0;", " private static StringBuilder buf_ = new StringBuilder(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(\"{\"))", " {", " onEmptyLine();", " buf_.append(s);", " _n_ = _n_ + INDENT_WIDTH;", " buf_.append(\"\\n\");", " indent();", " }", " else if (s.equals(\"(\") || s.equals(\"[\"))", " buf_.append(s);", " else if (s.equals(\")\") || s.equals(\"]\"))", " {", " removeTrailingWhitespace();", " buf_.append(s);", " buf_.append(\" \");", " }", " else if (s.equals(\"}\"))", " {", " _n_ = _n_ - INDENT_WIDTH;", " onEmptyLine();", " buf_.append(s);", " buf_.append(\"\\n\");", " indent();", " }", " else if (s.equals(\",\"))", " {", " removeTrailingWhitespace();", " buf_.append(s);", " buf_.append(\" \");", " }", " else if (s.equals(\";\"))", " {", " removeTrailingWhitespace();", " buf_.append(s);", " buf_.append(\"\\n\");", " indent();", " }", " else if (s.equals(\"\")) return;", " else if (s.trim().equals(\"\"))", " {", " backup();", " buf_.append(s);", " }", " else", " {", " buf_.append(s);", " buf_.append(\" \");", " }", " }" ] prEntryPoints :: String -> CF -> String prEntryPoints packageAbsyn cf = msg ++ concatMap prEntryPoint (allCatsNorm cf) ++ msg2 where msg = " // print and show methods are defined for each category.\n" msg2 = " /*** You shouldn't need to change anything beyond this point. ***/\n" prEntryPoint cat = unlines [ " public static String print(" ++ packageAbsyn ++ "." ++ cat' ++ " foo)", " {", " pp(foo, 0);", " trim();", " String temp = buf_.toString();", " buf_.delete(0,buf_.length());", " return temp;", " }", " public static String show(" ++ packageAbsyn ++ "." ++ cat' ++ " foo)", " {", " sh(foo);", " String temp = buf_.toString();", " buf_.delete(0,buf_.length());", " return temp;", " }" ] where cat' = identCat cat prData :: String -> [UserDef] -> (Cat, [Rule]) -> String prData packageAbsyn user (cat, rules) | isList cat = unlines $ concat [ [ " private static void pp(" ++ packageAbsyn ++ "." ++ dat ++ " foo, int _i_)" , " {" , " pp" ++ dat ++ "(foo.iterator(), _i_);" , " }" , "" , " private static void pp" ++ dat ++ "(java.util.Iterator<" ++ et ++ "> it, int _i_)" , " {" ] , map (" " ++) $ prList dat et rules , [ " }" , "" ] ] | otherwise = unlines $ [ " private static void pp(" ++ packageAbsyn ++ "." ++ dat +++ "foo, int _i_)" , " {" , concat (addElse $ map (prRule packageAbsyn) rules) , " }" ] where dat = identCat (normCat cat) et = typename packageAbsyn user $ identCat $ normCatOfList cat addElse = map (" " ++) . intersperse "else " . filter (not . null) . map (dropWhile isSpace) prRule :: String -> Rule -> String prRule packageAbsyn r@(Rule f _c cats _) | not (isCoercion f || isDefinedRule f) = concat [ " if (foo instanceof" +++ packageAbsyn ++ "." ++ fun ++ ")\n" , " {\n" , " " ++ packageAbsyn ++ "." ++ fun +++ fnm +++ "= (" ++ packageAbsyn ++ "." ++ fun ++ ") foo;\n" , lparen , cats' , rparen , " }\n" ] where fun = funName f 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 (render . prItem (text fnm)) (numVars cats) fnm = '_' : map toLower fun prRule _nm _ = "" prList :: String -> String -> [Rule] -> [String] prList dat et rules = concat [ if null docs0 then [ "if (it.hasNext())" ] else [ "if (!it.hasNext())" , "{ /* nil */" , render $ nest 4 $ vcat docs0 , "}" , "else" ] , if null docs1 then [ "{ /* cons */" , " " ++ et ++ " el = it.next();" ] else [ "{" , " " ++ et ++ " el = it.next();" , " if (!it.hasNext())" , " { /* last */" , render $ nest 4 $ vcat docs1 , " }" , " else" , " { /* cons */" ] , unlessNull (swRules isConsFun) $ \ docs -> [ render $ nest (if null docs1 then 2 else 4) $ vcat docs ] , unless (null docs1) [ " }" ] , [ "}" ] ] where prules = sortRulesByPrecedence rules swRules f = switchByPrecedence "_i_" $ map (second $ sep . map text . prListRule_ dat) $ uniqOn fst $ filter f prules -- Discard duplicates, can only handle one rule per precedence. docs0 = swRules isNilFun docs1 = swRules isOneFun -- | Only render the rhs (items) of a list rule. prListRule_ :: IsFun a => String -> Rul a -> [String] prListRule_ dat (Rule _ _ items _) = for items $ \case Right t -> "render(\"" ++ escapeChars t ++ "\");" Left (TokenCat "String") -> "printQuoted(el);" Left (ListCat _) -> "pp" ++ dat ++ "(it, _i_);" Left _ -> "pp(el, _i_);" -- | -- >>> prItem "F" (Right "++") -- render("++"); -- -- >>> prItem "F" (Left (TokenCat "String", "string_")) -- printQuoted(F.string_); -- -- >>> prItem "F" (Left (Cat "Abc", "abc_")) -- pp(F.abc_, 0); -- prItem :: Doc -> Either (Cat, Doc) String -> Doc prItem _ (Right t) = nest 7 ("render(\"" <> text(escapeChars t) <> "\");\n") prItem fnm (Left (TokenCat "String", nt)) = nest 7 ("printQuoted(" <> fnm <> "." <> nt <> ");\n") prItem fnm (Left (cat, nt)) = nest 7 ("pp(" <> fnm <> "." <> nt <> ", " <> integer (precCat cat) <> ");\n") --The following methods generate the Show function. shData :: String -> [UserDef] -> (Cat, [Rule]) -> String shData packageAbsyn user (cat, rules) = unlines k where k = if isList cat then [ " private static void sh(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo)" , " {" , shList packageAbsyn user cat rules ++ " }" ] else [ " private static void sh(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo)" , " {" , concatMap (shRule packageAbsyn) rules ++ " }" ] shRule :: String -> Rule -> String shRule packageAbsyn (Rule f _c cats _) | not (isCoercion f || isDefinedRule f) = unlines [ " if (foo instanceof" +++ packageAbsyn ++ "." ++ fun ++ ")" , " {" , " " ++ packageAbsyn ++ "." ++ fun +++ fnm +++ "= (" ++ packageAbsyn ++ "." ++ fun ++ ") foo;" , members ++ " }" ] where fun = funName f members = concat [ lparen , " render(\"" ++ escapeChars fun ++ "\");\n" , cats' , rparen ] cats' = if allTerms cats then "" else concatMap (render . shCat (text fnm)) (lefts (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 :: String -> [UserDef] -> Cat -> [Rule] -> String shList packageAbsyn user c _rules = unlines [ " for (java.util.Iterator<" ++ et ++ "> it = foo.iterator(); it.hasNext();)", " {", " sh(it.next());", " if (it.hasNext())", " render(\",\");", " }" ] where et = typename packageAbsyn user $ identCat $ normCatOfList c -- | -- >>> shCat "F" (ListCat (Cat "A"), "lista_") -- render("["); -- sh(F.lista_); -- render("]"); -- -- >>> shCat "F" (Cat "A", "a_") -- sh(F.a_); -- shCat :: Doc -> (Cat, Doc) -> Doc shCat fnm (ListCat _, vname) = vcat [ " render(\"[\");" , " sh(" <> fnm <> "." <> vname <> ");" , " render(\"]\");\n" ] shCat fname (_, vname) = " sh(" <> fname <> "." <> vname <> ");\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