diff --git a/docs/user_guide.rst b/docs/user_guide.rst index f2407b6e..69958049 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -16,6 +16,47 @@ Cup can only generate parsers with a single entry point. If multiple entry point are given using the ``entrypoint`` directive, only the first one will be used. Otherwise, the first category defined in the grammar file will be used as the entry point for the grammar. +If you need multiple entrypoints, use ANTLRv4. + +ANTLRv4 +....... + +`ANTLRv4 `_ is a parser generator for Java. + +With the ``--antlr`` option BNFC generates an ANTLRv4 parser and lexer. + +All categories can be entrypoints with ANTLR: the ``entrypoint`` directive is +thus ignored. + +Make sure that your system's Java classpath variable points to an ANTLRv4 jar +(`download here `_) + +You can use the ANTLR parser generator as follows:: + + bnfc --java --antlr -m Calc.cf + make + +ANTLRv4 returns by default a `parse tree`, which enables you to make use of the +analysis facilities that ANTLR offers. +You can of course still get the usual AST built with the abstract syntax classes +generated by BNFC. + +From the ``Calc/Test.java``, generated as a result of the previous commands:: + + public Calc.Absyn.Exp parse() throws Exception + { + /* The default parser is the first-defined entry point. */ + CalcParser.ExpContext pc = p.exp(); + // some code in between + Calc.Absyn.Exp ast = pc.result; + +The ``pc`` object is a ``ParserRuleContext`` object returned by ANTLR. + +It can be used for further analysis through the ANTLR API. + +The usual abstract syntax tree returned by BNFC is in the ``result`` field of +any ``ParserRuleContext`` returned by the available parse functions +(here ``exp()``). Pygments ======== @@ -40,7 +81,7 @@ script ``setup.py`` for the second option so you can start using the highlighter right away without fiddling with pygments code. Here is an example (assuming you've put the Calc grammar in the current -directony):: +directory):: virtualenv myenv # If you don't use virtualenv, skip the first two steps source myenv/bin/activate diff --git a/source/src/BNFC/Backend/Common/Makefile.hs b/source/src/BNFC/Backend/Common/Makefile.hs index 90b02473..8cc785d2 100644 --- a/source/src/BNFC/Backend/Common/Makefile.hs +++ b/source/src/BNFC/Backend/Common/Makefile.hs @@ -29,6 +29,17 @@ mkRule target deps recipes = mkVar :: String -> String -> Doc mkVar n v = text n <> "=" <> text v +--- | Variable referencing +-- +-- >>> mkRefVar "FOO" +-- ${FOO} +mkRefVar :: String -> Doc +mkRefVar m = case m of + "" -> "" + _ -> text $ refVar m + +refVar :: String -> String +refVar m = "${" ++ m ++ "}" -- | Create the Makefile file using the name specified in the option -- record. diff --git a/source/src/BNFC/Backend/Common/NamedVariables.hs b/source/src/BNFC/Backend/Common/NamedVariables.hs index 55ad0dbc..f07f4d55 100644 --- a/source/src/BNFC/Backend/Common/NamedVariables.hs +++ b/source/src/BNFC/Backend/Common/NamedVariables.hs @@ -163,3 +163,8 @@ varName c = map toLower c ++ "_" --this makes var names a little cleaner. showNum n = if n == 0 then [] else show n + +-- Makes the first letter a lowercase. +firstLowerCase :: String -> String +firstLowerCase "" = "" +firstLowerCase (a:b) = (toLower a):b \ No newline at end of file diff --git a/source/src/BNFC/Backend/Java.hs b/source/src/BNFC/Backend/Java.hs index 0c3dc526..7527b1da 100644 --- a/source/src/BNFC/Backend/Java.hs +++ b/source/src/BNFC/Backend/Java.hs @@ -38,13 +38,17 @@ module BNFC.Backend.Java ( makeJava ) where ------------------------------------------------------------------- -- Dependencies. ------------------------------------------------------------------- -import System.FilePath (pathSeparator) +import System.FilePath (pathSeparator, isPathSeparator) +import Data.List ( intersperse ) import BNFC.Utils import BNFC.CF import BNFC.Options as Options import BNFC.Backend.Base +import BNFC.Backend.Java.Utils import BNFC.Backend.Java.CFtoCup15 ( cf2Cup ) import BNFC.Backend.Java.CFtoJLex15 +import BNFC.Backend.Java.CFtoAntlr4Lexer +import BNFC.Backend.Java.CFtoAntlr4Parser import BNFC.Backend.Java.CFtoJavaAbs15 ( cf2JavaAbs ) import BNFC.Backend.Java.CFtoJavaPrinter15 import BNFC.Backend.Java.CFtoVisitSkel15 @@ -52,183 +56,589 @@ import BNFC.Backend.Java.CFtoComposVisitor import BNFC.Backend.Java.CFtoAbstractVisitor import BNFC.Backend.Java.CFtoFoldVisitor import BNFC.Backend.Java.CFtoAllVisitor +import BNFC.Backend.Common.NamedVariables (SymEnv, firstLowerCase) import qualified BNFC.Backend.Common.Makefile as Makefile import BNFC.PrettyPrint ------------------------------------------------------------------- -- | Build the Java output. --- FIXME: get everything to put the files in the right places. --- Adapt Makefile to do the business. ------------------------------------------------------------------- + +-- | This creates the Java files. makeJava :: SharedOptions -> CF -> MkFiles () makeJava options@Options{..} cf = do -- Create the package directories if necessary. - let packageBase = case inPackage of + let packageBase = case inPackage of Nothing -> lang Just p -> p ++ "." ++ lang - packageAbsyn = packageBase ++ "." ++ "Absyn" - dirBase = pkgToDir packageBase - dirAbsyn = pkgToDir packageAbsyn - let absynFiles = remDups $ cf2JavaAbs packageBase packageAbsyn cf - absynBaseNames = map fst absynFiles - absynFileNames = map (dirAbsyn ++) absynBaseNames - let writeAbsyn (filename, contents) = + packageAbsyn = packageBase ++ "." ++ "Absyn" + dirBase = pkgToDir packageBase + dirAbsyn = pkgToDir packageAbsyn + javaex str = dirBase ++ str +.+ "java" + bnfcfiles = bnfcVisitorsAndTests packageBase packageAbsyn cf + cf2JavaPrinter + cf2VisitSkel + cf2ComposVisitor + cf2AbstractVisitor + cf2FoldVisitor + cf2AllVisitor + (testclass parselexspec + (head $ results lexmake) -- lexer class + (head $ results parmake) -- parser class + ) + makebnfcfile x = mkfile (javaex (fst $ x bnfcfiles)) + (snd $ x bnfcfiles) + + let absynFiles = remDups $ cf2JavaAbs packageBase packageAbsyn cf + absynBaseNames = map fst absynFiles + absynFileNames = map (dirAbsyn ++) absynBaseNames + let writeAbsyn (filename, contents) = mkfile (dirAbsyn ++ filename ++ ".java") contents - mapM_ writeAbsyn absynFiles - mkfile (dirBase ++ "PrettyPrinter.java") $ cf2JavaPrinter packageBase packageAbsyn cf - mkfile (dirBase ++ "VisitSkel.java") $ cf2VisitSkel packageBase packageAbsyn cf - mkfile (dirBase ++ "ComposVisitor.java") $ cf2ComposVisitor packageBase packageAbsyn cf - mkfile (dirBase ++ "AbstractVisitor.java") $ cf2AbstractVisitor packageBase packageAbsyn cf - mkfile (dirBase ++ "FoldVisitor.java") $ cf2FoldVisitor packageBase packageAbsyn cf - mkfile (dirBase ++ "AllVisitor.java") $ cf2AllVisitor packageBase packageAbsyn cf - mkfile (dirBase ++ "Test.java") $ javaTest packageBase packageAbsyn cf ---- mkfile ("Test" ++ name) $ "java " ++ dirBase ++ "Test $(1)" - let (lex, env) = cf2jlex packageBase cf jflex - mkfile (dirBase ++ "Yylex") lex - liftIO $ putStrLn " (Tested with JLex 1.2.6.)" - mkfile (dirBase ++ lang ++ ".cup") $ cf2Cup packageBase packageAbsyn cf env - liftIO $ putStrLn $ " (Parser created for category " ++ show (firstEntry cf) ++ ")" - liftIO $ putStrLn " (Tested with CUP 0.10k)" - Makefile.mkMakefile options $ makefile lang dirBase dirAbsyn absynFileNames jflex + mapM_ writeAbsyn absynFiles + makebnfcfile bprettyprinter + makebnfcfile bskel + makebnfcfile bcompos + makebnfcfile babstract + makebnfcfile bfold + makebnfcfile ball + makebnfcfile btest + let (lex, env) = lexfun packageBase cf + -- Where the lexer file is created. lex is the content! + mkfile (dirBase ++ inputfile lexmake ) lex + liftIO $ putStrLn $ " (Tested with "+++ toolname lexmake + +++ toolversion lexmake +++")" + -- where the parser file is created. + mkfile (dirBase ++ inputfile parmake) + $ parsefun packageBase packageAbsyn cf env + liftIO $ putStrLn (if supportsEntryPoints parmake then + "(Parser created for all categories)" else + " (Parser created only for category " ++ + show (firstEntry cf) ++ ")") + liftIO $ putStrLn $ " (Tested with " +++ toolname parmake + +++ toolversion parmake +++ ")" + Makefile.mkMakefile options $ + makefile dirBase dirAbsyn absynFileNames parselexspec where remDups [] = [] remDups ((a,b):as) = case lookup a as of Just {} -> remDups as Nothing -> (a, b) : remDups as - pkgToDir :: String -> FilePath pkgToDir s = replace '.' pathSeparator s ++ [pathSeparator] + parselexspec = parserLexerSelector lang javaLexerParser + lexfun = cf2lex $ lexer parselexspec + parsefun = cf2parse $ parser parselexspec + parmake = makeparserdetails (parser parselexspec) + lexmake = makelexerdetails (lexer parselexspec) --- FIXME It's almost certainly better to just feed all the Java source --- files to javac in one go. --- Replace with an ANT script? -makefile :: String -> FilePath -> FilePath -> [String] -> Bool -> Doc -makefile name dirBase dirAbsyn absynFileNames jflex = vcat - [ Makefile.mkVar "JAVAC" "javac" - , Makefile.mkVar "JAVAC_FLAGS" "-sourcepath ." - , Makefile.mkVar "JAVA" "java" - , Makefile.mkVar "JAVA_FLAGS" "" - , Makefile.mkVar "CUP" "java_cup.Main" - , Makefile.mkVar "CUPFLAGS" "-nopositions -expect 100" - , if jflex then Makefile.mkVar "JFLEX" "jflex" - else Makefile.mkVar "JLEX" "JLex.Main" - , Makefile.mkRule "all" [ "test" ] - [] - , Makefile.mkRule "test" ("absyn" : classes) - [] - , Makefile.mkRule ".PHONY" ["absyn"] - [] - , Makefile.mkRule "%.class" [ "%.java" ] - [ "${JAVAC} ${JAVAC_FLAGS} $^" ] - , Makefile.mkRule "absyn" [absynJavaSrc] - [ "${JAVAC} ${JAVAC_FLAGS} $^" ] - , Makefile.mkRule (dirBase ++ "Yylex.java") [ dirBase ++ "Yylex" ] - [ (if jflex then "${JFLEX} " else "${JAVA} ${JAVA_FLAGS} ${JLEX} ") ++ dirBase ++ "Yylex" ] - , Makefile.mkRule (dirBase ++ "sym.java " ++ dirBase ++ "parser.java") - [ dirBase ++ name ++ ".cup" ] - [ "${JAVA} ${JAVA_FLAGS} ${CUP} ${CUPFLAGS} " ++ dirBase ++ name ++ ".cup" - , "mv sym.java parser.java " ++ dirBase ] - , Makefile.mkRule (dirBase ++ "Yylex.class") [ dirBase ++ "Yylex.java", - dirBase ++ "sym.java" ] - [] - , Makefile.mkRule (dirBase ++ "sym.class") [ dirBase ++ "sym.java" ] - [] - , Makefile.mkRule (dirBase ++ "parser.class") [ dirBase ++ "parser.java" - , dirBase ++ "sym.java" ] - [] - , Makefile.mkRule (dirBase ++ "PrettyPrinter.class") - [ dirBase ++ "PrettyPrinter.java" ] - [] - , Makefile.mkRule "clean" [] - [ "rm -f " ++ dirAbsyn ++ "*.class" ++ " " ++ dirBase ++ "*.class" ] - , Makefile.mkRule "distclean" [ "vclean" ] - [] - , Makefile.mkRule "vclean" [] - [ " rm -f " ++ absynJavaSrc ++ " " ++ absynJavaClass - , " rm -f " ++ dirAbsyn ++ "*.class" - -- , "rm -f " ++ "Test" ++ name - , " rmdir " ++ dirAbsyn - , " rm -f " ++ unwords (map (dirBase ++) [ - "Yylex", - name ++ ".cup", - "Yylex.java", - "VisitSkel.java", - "ComposVisitor.java", - "AbstractVisitor.java", - "FoldVisitor.java", - "AllVisitor.java", - "PrettyPrinter.java", - "Skeleton.java", - "Test.java", - "sym.java", - "parser.java", - "*.class"]) - , "rm -f Makefile" - , "rmdir -p " ++ dirBase ] - ] +makefile :: FilePath -> FilePath -> [String] -> ParserLexerSpecification -> Doc +makefile dirBase dirAbsyn absynFileNames jlexpar = vcat $ + makeVars [ ("JAVAC", "javac"), + ("JAVAC_FLAGS", "-sourcepath ."), + ( "JAVA", "java"), + ( "JAVA_FLAGS", ""), + -- parser executable + ( "PARSER", executable parmake), + -- parser flags + ( "PARSER_FLAGS", flags parmake dirBase), + -- lexer executable (and flags?) + ( "LEXER", executable lexmake), + ( "LEXER_FLAGS", flags lexmake dirBase) + ] + ++ + makeRules [ ("all", [ "test" ], []), + ( "test", "absyn" : classes, []), + ( ".PHONY", ["absyn"], []), + ("%.class", [ "%.java" ], [ runJavac "$^" ]), + ("absyn", [absynJavaSrc],[ runJavac "$^" ]) + ]++ + [-- running the lexergen: output of lexer -> input of lexer : calls lexer + let ff = filename lexmake -- name of input file without extension + dirBaseff = dirBase ++ ff -- prepend directory + inp = dirBase ++ inputfile lexmake in + Makefile.mkRule (dirBaseff +.+ "java") [ inp ] + [ "${LEXER} ${LEXER_FLAGS} "++ inp ] + + -- running the parsergen, these there are its outputs + -- output of parser -> input of parser : calls parser + , let inp = dirBase ++ inputfile parmake in + Makefile.mkRule (unwords (map (dirBase++) (dotJava $ results parmake))) + [ inp ] $ + ("${PARSER} ${PARSER_FLAGS} " ++ inp) : + ["mv " ++ unwords (dotJava $ results parmake) +++ dirBase + | moveresults parmake] + -- Class of the output of lexer generator wants java of : + -- output of lexer and parser generator + , let lexerOutClass = dirBase ++ filename lexmake +.+ "class" + outname x = dirBase ++ x +.+ "java" + deps = map outname (results lexmake ++ results parmake) in + Makefile.mkRule lexerOutClass deps [] + ]++ + reverse [Makefile.mkRule tar dep [] | + (tar,dep) <- partialParserGoals dirBase (results parmake)] + ++[ Makefile.mkRule (dirBase ++ "PrettyPrinter.class") + [ dirBase ++ "PrettyPrinter.java" ] [] + -- Removes all the class files created anywhere + , Makefile.mkRule "clean" [] [ "rm -f " ++ dirAbsyn ++ "*.class" ++ " " + ++ dirBase ++ "*.class" ] + -- Remains the same + , Makefile.mkRule "distclean" [ "vclean" ] [] + -- removes everything + , Makefile.mkRule "vclean" [] + [ " rm -f " ++ absynJavaSrc ++ " " ++ absynJavaClass + , " rm -f " ++ dirAbsyn ++ "*.class" + , " rmdir " ++ dirAbsyn + , " rm -f " ++ unwords (map (dirBase ++) $ + [ inputfile lexmake + , inputfile parmake + ] + ++ dotJava (results lexmake) + ++ [ "VisitSkel.java" + , "ComposVisitor.java" + , "AbstractVisitor.java" + , "FoldVisitor.java" + , "AllVisitor.java" + , "PrettyPrinter.java" + , "Skeleton.java" + , "Test.java" + ] + ++ dotJava (results parmake) + ++["*.class"]) + , " rm -f Makefile" + , " rmdir -p " ++ dirBase ] + ] where - absynJavaSrc = unwords (map (++ ".java") absynFileNames) - absynJavaClass = unwords (map (++ ".class") absynFileNames) - classes = map (dirBase ++) - [ "Yylex.class", "PrettyPrinter.class", "Test.class" + makeVars x = [Makefile.mkVar n v | (n,v) <- x] + makeRules x = [Makefile.mkRule tar dep recipe | (tar, dep, recipe) <- x] + parmake = makeparserdetails (parser jlexpar) + lexmake = makelexerdetails (lexer jlexpar) + absynJavaSrc = unwords (dotJava absynFileNames) + absynJavaClass = unwords (dotClass absynFileNames) + classes = prependPath dirBase lst + lst = dotClass (results lexmake) ++ [ "PrettyPrinter.class", "Test.class" , "ComposVisitor.class", "AbstractVisitor.class" - , "FoldVisitor.class", "AllVisitor.class", "parser.class" - , "sym.class", "Test.class"] - - -javaTest :: String -> String -> CF -> Doc -javaTest packageBase packageAbsyn cf = vcat - [ "package" <+> text packageBase <> ";" - , "import java_cup.runtime.*;" - , "import" <+> text packageBase <> ".*;" - , "import" <+> text packageAbsyn <> ".*;" - , "import java.io.*;" - , "" - , "public class Test" - , codeblock 2 - [ "public static void main(String args[]) throws Exception" + , "FoldVisitor.class", "AllVisitor.class"]++ + dotClass (results parmake) ++ ["Test.class"] + +type TestClass = String + -- ^ class of the lexer + -> String + -- ^ class of the parser + -> String + -- ^ package where the non-abstract syntax classes are created + -> String + -- ^ package where the abstract syntax classes are created + -> CF + -- ^ the CF bundle + -> String + +-- | Test class details for J(F)Lex + CUP +cuptest :: TestClass +cuptest = javaTest ["java_cup.runtime"] + "Throwable" + (const []) + (\x i -> x <> i <> ";") + (\x i -> x <> i <> ";") + showOpts + (\_ pabs enti -> + pabs <> "." <> enti <+> "ast = p."<> "p" <> enti + <> "();") + locs + where locs = "At line \" + String.valueOf(t.l.line_num()) + \"," + ++ " near \\\"\" + t.l.buff() + \"\\\" :" + showOpts _ = ["not available."] + + + +-- | Test class details for ANTLR4 +antlrtest :: TestClass +antlrtest = javaTest [ "org.antlr.v4.runtime","org.antlr.v4.runtime.atn" + , "org.antlr.v4.runtime.dfa","java.util" + ] + "TestError" + antlrErrorHandling + (\x i -> vcat + [ x <> "(new ANTLRInputStream" <> i <>");" + , "l.addErrorListener(new BNFCErrorListener());" + ]) + (\x i -> vcat + [x <> "(new CommonTokenStream" <> i <>");" + , "p.addErrorListener(new BNFCErrorListener());" + ]) + showOpts + (\pbase pabs enti -> vcat + [ + let rulename = getRuleName (show enti) + typename = text rulename + methodname = text $ firstLowerCase rulename + in + pbase <> "." <> typename <> "Context pc = p." + <> methodname <> "();" + , "org.antlr.v4.runtime.Token _tkn = p.getInputStream()" + <> ".getTokenSource().nextToken();" + , "if(_tkn.getType() != -1) throw new TestError" + <> "(\"Stream does not end with EOF\"," + <> "_tkn.getLine(),_tkn.getCharPositionInLine());", + pabs <> "." <> enti <+> "ast = pc.result;" + ]) + "At line \" + e.line + \", column \" + e.column + \" :" + where showOpts [] = [] + showOpts (x:xs) | normCat x /= x = showOpts xs + | otherwise = text (firstLowerCase $ identCat x) : showOpts xs + +parserLexerSelector :: String -> JavaLexerParser -> ParserLexerSpecification +parserLexerSelector _ JLexCup = ParseLexSpec + { lexer = cf2JLex + , parser = cf2cup + , testclass = cuptest + } +parserLexerSelector _ JFlexCup = + (parserLexerSelector "" JLexCup){lexer = cf2JFlex} +parserLexerSelector l Antlr4 = ParseLexSpec + { lexer = cf2AntlrLex' l + , parser = cf2AntlrParse' l + , testclass = antlrtest + } + +data ParserLexerSpecification = ParseLexSpec + { parser :: CFToParser + , lexer :: CFToLexer + , testclass :: TestClass + } + +-- |CF -> LEXER GENERATION TOOL BRIDGE +-- | function translating the CF to an appropriate lexer generation tool. +type CF2LexerFunction = String -> CF -> (Doc, SymEnv) + +-- Chooses the translation from CF to the lexer +data CFToLexer = CF2Lex + { cf2lex :: CF2LexerFunction + , makelexerdetails :: MakeFileDetails + } + +-- | Instances of cf-lexergen bridges +cf2JLex, cf2JFlex :: CFToLexer + +cf2JLex = CF2Lex + { cf2lex = BNFC.Backend.Java.CFtoJLex15.cf2jlex False + , makelexerdetails = jlexmakedetails + } + +cf2JFlex = CF2Lex + { cf2lex = BNFC.Backend.Java.CFtoJLex15.cf2jlex True + , makelexerdetails = jflexmakedetails + } + +cf2AntlrLex' :: String -> CFToLexer +cf2AntlrLex' l = CF2Lex + { cf2lex = + BNFC.Backend.Java.CFtoAntlr4Lexer.cf2AntlrLex + , makelexerdetails = antlrmakedetails $ l++"Lexer" + } + +-- | CF -> PARSER GENERATION TOOL BRIDGE +-- | function translating the CF to an appropriate parser generation tool. +type CF2ParserFunction = String -> String -> CF -> SymEnv -> String + +-- | Chooses the translation from CF to the parser +data CFToParser = CF2Parse + { cf2parse :: CF2ParserFunction + , makeparserdetails :: MakeFileDetails + } + +-- | Instances of cf-parsergen bridges +cf2cup :: CFToParser +cf2cup = CF2Parse + { cf2parse = BNFC.Backend.Java.CFtoCup15.cf2Cup + , makeparserdetails = cupmakedetails + } + +cf2AntlrParse' :: String -> CFToParser +cf2AntlrParse' l = CF2Parse + { cf2parse = + BNFC.Backend.Java.CFtoAntlr4Parser.cf2AntlrParse + , makeparserdetails = antlrmakedetails $ l++"Parser" + } + + +-- | shorthand for Makefile command running javac or java +runJavac , runJava:: String -> String +runJava = mkRunProgram "JAVA" +runJavac = mkRunProgram "JAVAC" + +-- | function returning a string executing a program contained in a variable j +-- on input s +mkRunProgram :: String -> String -> String +mkRunProgram j s = Makefile.refVar j +++ Makefile.refVar (j +-+ "FLAGS") +++ s + +type OutputDirectory = String + +-- | MAKEFILE DETAILS FROM RUNNING THE PARSER-LEXER GENERATION TOOLS +data MakeFileDetails = MakeDetails + { -- | The string that executes the generation tool + executable :: String + -- | Flags to pass to the tool + , flags :: OutputDirectory -> String + -- | Input file to the tool + , filename :: String + -- | Extension of input file to the tool + , fileextension :: String + -- | name of the tool + , toolname :: String + -- | Tool version + , toolversion :: String + -- | true if the tool is a parser and supports entry points, + -- false otherwise + , supportsEntryPoints :: Bool + -- | list of names (without extension!) of files resulting from the + -- application of the tool which are relevant to a make rule + , results :: [String] + -- | if true, the files are moved to the base directory, otherwise + -- they are left where they are + , moveresults :: Bool + } + + + +mapEmpty :: a->String +mapEmpty _ = "" + +-- Instances of makefile details. +cupmakedetails, jflexmakedetails, jlexmakedetails :: MakeFileDetails + +jlexmakedetails = MakeDetails + { executable = runJava "JLex.Main" + , flags = mapEmpty + , filename = "Yylex" + , fileextension = "" + , toolname = "JLex" + , toolversion = "1.2.6." + , supportsEntryPoints = False + , results = ["Yylex"] + , moveresults = False + } + +jflexmakedetails = jlexmakedetails + { executable = "jflex" + , toolname = "JFlex" + , toolversion = "1.4.3" + } + +cupmakedetails = MakeDetails + { executable = runJava "java_cup.Main" + , flags = const "-nopositions -expect 100" + , filename = "_cup" + , fileextension = "cup" + , toolname = "CUP" + , toolversion = "0.10k" + , supportsEntryPoints = False + , results = ["parser", "sym"] + , moveresults = True + } + + +antlrmakedetails :: String -> MakeFileDetails +antlrmakedetails l = MakeDetails + { executable = runJava "org.antlr.v4.Tool" + , flags = \x -> unwords $ + let path = take (length x - 1) x + pointed = map cnv path + cnv y = if isPathSeparator y + then '.' + else y + in [ "-lib", path + , "-package", pointed] + , filename = l + , fileextension = "g4" + , toolname = "ANTLRv4" + , toolversion = "4.5.1" + , supportsEntryPoints = True + , results = [l] + , moveresults = False + } + +prependPath , appendExtension :: String -> [String] -> [String] +prependPath s fi = [s ++ x | x<- fi] +appendExtension s fi = [x+.+s | x<- fi] + +dotJava,dotClass :: [String] -> [String] +dotJava = appendExtension "java" +dotClass = appendExtension "class" + +type CFToJava = String -> String -> CF -> String +-- | Contains the pairs filename/content for all the non-abstract syntax files +-- generated by BNFC +data BNFCGeneratedEntities = BNFCGenerated + { bprettyprinter :: (String, String) + , btest :: (String, String) + , bcompos :: (String, String) + , babstract :: (String, String) + , bfold :: (String, String) + , ball :: (String, String) + , bskel :: (String, String) + } + +bnfcVisitorsAndTests :: String -> String -> CF -> + CFToJava -> CFToJava -> CFToJava -> + CFToJava -> CFToJava -> CFToJava -> + CFToJava -> BNFCGeneratedEntities +bnfcVisitorsAndTests pbase pabsyn cf cf0 cf1 cf2 cf3 cf4 cf5 cf6 = + BNFCGenerated + { bprettyprinter = ( "PrettyPrinter" , app cf0) + , bskel = ( "VisitSkel", app cf1) + , bcompos = ( "ComposVisitor" , app cf2) + , babstract = ( "AbstractVisitor" , app cf3) + , bfold = ( "FoldVisitor", app cf4) + , ball = ( "AllVisitor", app cf5) + , btest = ( "Test" , app cf6) + } + where app x = x pbase pabsyn cf + +inputfile x = filename x ++ case fileextension x of + "" -> "" + a -> '.':a + +-- | constructs the rules regarding the parser in the makefile +partialParserGoals :: String -> [String] -> [(String, [String])] +partialParserGoals _ [] = [] +partialParserGoals dbas (x:rest) = + (dbas++x+.+"class",map (\y ->dbas++y+.+"java")(x:rest)) + :partialParserGoals dbas rest + +-- | Creates the Test.java class. +javaTest :: [Doc] + -- ^ list of imported packages + -> String + -- ^ name of the exception thrown in case of parsing failure + -> (String -> [Doc]) + -- ^ handler for the exception thrown + -> (Doc -> Doc -> Doc) + -- ^ function formulating the construction of the lexer object + -> (Doc -> Doc -> Doc) + -- ^ as above, for parser object + -> ([Cat] -> [Doc]) + -- ^ Function processing the names of the methods corresponding + -- to entry points + -> (Doc -> Doc -> Doc -> Doc) + -- ^ function formulating the invocation of the parser tool within + -- Java + -> String + -- ^ error string output in consequence of a parsing failure + -> TestClass +javaTest imports + err + errhand + lexerconstruction + parserconstruction + showOpts + invocation + errmsg + lexer + parser + packageBase + packageAbsyn + cf = + render $ vcat $ + [ "package" <+> text packageBase <> ";" + , "import" <+> text packageBase <> ".*;" + , "import" <+> text packageAbsyn <> ".*;" + , "import java.io.*;" + ] + ++ map importfun imports + ++ errhand err + ++[ "" + , "public class Test" , codeblock 2 - [ "Yylex l = null;" - , "parser p;" - , "try" + [ lx <+> "l;" + , px <+> "p;" + , "" + , "public Test(String[] args)" + , codeblock 2 [ + "try" + , codeblock 2 [ "Reader input;" + , "if (args.length == 0)" + <> "input = new InputStreamReader(System.in);" + , "else input = new FileReader(args[0]);" + , "l = new "<>lexerconstruction lx "(input)" + ] + , "catch(IOException e)" + , codeblock 2 [ "System.err.println" + <>"(\"Error: File not found: \" + args[0]);" + , "System.exit(1);" + ] + , "p = new "<> parserconstruction px "(l)" + ] + , "" + , "public" <+> text packageAbsyn <> "." <> absentity + <+>"parse() throws Exception" , codeblock 2 - [ "if (args.length == 0) l = new Yylex(new InputStreamReader(System.in));" - , "else l = new Yylex(new FileReader(args[0]));" ] - , "catch(FileNotFoundException e)" - , "{" - , " System.err.println(\"Error: File not found: \" + args[0]);" - , " System.exit(1);" - , "}" - , "p = new parser(l);" - , "/* The default parser is the first-defined entry point. */" - , "/* You may want to change this. Other options are: */" - , "/* " <> fsep (punctuate "," (showOpts (tail eps))) <> " */" - , "try" - , "{" - , " " <> text packageAbsyn <> "." <> text (show def) <+> "parse_tree = p.p" - <> text (show def) <> "();" - , " System.out.println();" - , " System.out.println(\"Parse Succesful!\");" - , " System.out.println();" - , " System.out.println(\"[Abstract Syntax]\");" - , " System.out.println();" - , " System.out.println(PrettyPrinter.show(parse_tree));" - , " System.out.println();" - , " System.out.println(\"[Linearized Tree]\");" - , " System.out.println();" - , " System.out.println(PrettyPrinter.print(parse_tree));" - , "}" - , "catch(Throwable e)" - , "{" - , " System.err.println(\"At line \" + String.valueOf(l.line_num()) + \", near \\\"\" + l.buff() + \"\\\" :\");" - , " System.err.println(\" \" + e.getMessage());" - , " System.exit(1);" - , "}" + [ "/* The default parser is the first-defined entry point. */" + , "/* Other options are: */" + , "/* " <> fsep (punctuate "," (showOpts (tail eps))) <> " */" + , invocation px (text packageAbsyn) absentity + , printOuts [ "\"Parse Succesful!\"" + , "\"[Abstract Syntax]\"" + , "PrettyPrinter.show(ast)" + , "\"[Linearized Tree]\"" + , "PrettyPrinter.print(ast)" + ] + , "return ast;" + ] + , "" + , "public static void main(String args[]) throws Exception" + , codeblock 2 [ "Test t = new Test(args);" + , "try" + , codeblock 2 [ "t.parse();" ] + ,"catch("<>text err<+>"e)" + , codeblock 2 [ "System.err.println(\""<>text errmsg<>"\");" + , "System.err.println(\" \" + e.getMessage());" + , "System.exit(1);" + ] + ] + ] + ] + where + printOuts x = vcat $ map javaPrintOut (messages x) + messages x = "" : intersperse "" x + javaPrintOut x = text $ "System.out.println(" ++ x ++ ");" + importfun x = "import" <+> x <> ".*;" + lx = text lexer + px = text parser + absentity = text $ show def + eps = allEntryPoints cf + def = head eps + +-- | Error handling in ANTLR. +-- By default, ANTLR does not stop after any parsing error and attempts to go +-- on, delivering what it has been able to parse. +-- It does not throw any exception, unlike J(F)lex+CUP. +-- The below code makes the test class behave as with J(F)lex+CUP. +antlrErrorHandling :: String -> [Doc] +antlrErrorHandling te = [ "class"<+>tedoc<+>"extends RuntimeException" + , codeblock 2 [ "int line;" + , "int column;" + , "public"<+>tedoc<>"(String msg, int l, int c)" + , codeblock 2 [ "super(msg);" + , "line = l;" + , "column = c;" ] ] + , "class BNFCErrorListener implements ANTLRErrorListener" + , codeblock 2 [ "@Override" + , "public void syntaxError(Recognizer recognizer, Object o, int i" + <> ", int i1, String s, RecognitionException e)" + , codeblock 2 [ "throw new"<+>tedoc<>"(s,i,i1);"] + , "@Override" + , "public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, " + <>"boolean b, BitSet bitSet, ATNConfigSet atnConfigSet)" + , codeblock 2[ "throw new"<+>tedoc<>"(\"Ambiguity at\",i,i1);" ] + , "@Override" + , "public void reportAttemptingFullContext(Parser parser, DFA dfa, " + <>"int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet)" + , codeblock 2 [] + , "@Override" + ,"public void reportContextSensitivity(Parser parser, DFA dfa, int i, " + <>"int i1, int i2, ATNConfigSet atnConfigSet)" + ,codeblock 2 [] + ] ] - where - eps = allEntryPoints cf - def = head eps - showOpts [] = [] - showOpts (x:xs) | normCat x /= x = showOpts xs - | otherwise = text ('p' : identCat x) : showOpts xs + where tedoc = text te diff --git a/source/src/BNFC/Backend/Java/CFtoAbstractVisitor.hs b/source/src/BNFC/Backend/Java/CFtoAbstractVisitor.hs index f5735a14..5fb6f401 100644 --- a/source/src/BNFC/Backend/Java/CFtoAbstractVisitor.hs +++ b/source/src/BNFC/Backend/Java/CFtoAbstractVisitor.hs @@ -26,25 +26,26 @@ import BNFC.Backend.Common.NamedVariables cf2AbstractVisitor :: String -> String -> CF -> String cf2AbstractVisitor packageBase packageAbsyn cf = - unlines [ - "package" +++ packageBase ++ ";", - "import" +++ packageAbsyn ++ ".*;", - "/** BNFC-Generated Abstract Visitor */", - "public class AbstractVisitor implements AllVisitor {", - concatMap (prData packageAbsyn user) groups, - "}"] + unlines [ "package" +++ packageBase ++ ";" + , "import" +++ packageAbsyn ++ ".*;" + , "/** BNFC-Generated Abstract Visitor */" + , "public class AbstractVisitor implements AllVisitor {" + , concatMap (prData packageAbsyn user) groups + , "}"] where user = fst (unzip (tokenPragmas cf)) - groups = [ g | g@(c,_) <- fixCoercions (ruleGroupsInternals cf), not (isList c) ] + groups = [ g + | g@(c,_) <- fixCoercions (ruleGroupsInternals cf), not (isList c) ] --Traverses a category based on its type. prData :: String -> [UserDef] -> (Cat, [Rule]) -> String prData packageAbsyn user (cat, rules) = unlines $ ["/* " ++ identCat cat ++ " */"] - ++ map (prRule packageAbsyn user cat) rules - ++ [" public R visitDefault(" ++ q ++ " p, A arg) {", - " throw new IllegalArgumentException(this.getClass().getName() + \": \" + p);", - " }"] + ++ map (prRule packageAbsyn user cat) rules + ++ [" public R visitDefault(" ++ q ++ " p, A arg) {" + , " throw new IllegalArgumentException(this.getClass()" + ++ ".getName() + \": \" + p);" + , " }"] where q = packageAbsyn ++ "." ++ identCat cat --traverses a standard rule. @@ -52,6 +53,6 @@ prRule :: String -> [UserDef] -> Cat -> Rule -> String prRule packageAbsyn _ _ (Rule fun _ _) | not (isCoercion fun || isDefinedRule fun) = " public R visit(" ++ cls ++ " p, A arg) { return visitDefault(p, arg); }" - where cls = packageAbsyn ++ "." ++ fun + where cls = packageAbsyn ++ "." ++ fun prRule _ _ _ _ = "" diff --git a/source/src/BNFC/Backend/Java/CFtoAllVisitor.hs b/source/src/BNFC/Backend/Java/CFtoAllVisitor.hs index a0527445..1ba9188a 100644 --- a/source/src/BNFC/Backend/Java/CFtoAllVisitor.hs +++ b/source/src/BNFC/Backend/Java/CFtoAllVisitor.hs @@ -20,10 +20,11 @@ module BNFC.Backend.Java.CFtoAllVisitor (cf2AllVisitor) where +import Data.List import BNFC.CF import BNFC.Utils ((+++)) import BNFC.Backend.Common.NamedVariables -import Data.List + cf2AllVisitor :: String -> String -> CF -> String cf2AllVisitor packageBase packageAbsyn cf = @@ -37,8 +38,9 @@ cf2AllVisitor packageBase packageAbsyn cf = intercalate ",\n" $ map (" "++) is, "{}"] where - groups = [ g | g@(c,_) <- fixCoercions (ruleGroupsInternals cf), not (isList c) ] - is = map (prInterface packageAbsyn) groups + groups = [ g + | g@(c,_) <- fixCoercions (ruleGroupsInternals cf), not (isList c) ] + is = map (prInterface packageAbsyn) groups prInterface :: String -> (Cat, [Rule]) -> String prInterface packageAbsyn (cat, _) = diff --git a/source/src/BNFC/Backend/Java/CFtoAntlr4Lexer.hs b/source/src/BNFC/Backend/Java/CFtoAntlr4Lexer.hs new file mode 100644 index 00000000..ae812666 --- /dev/null +++ b/source/src/BNFC/Backend/Java/CFtoAntlr4Lexer.hs @@ -0,0 +1,200 @@ +{- + BNF Converter: Java Antlr4 Lexer generator + Copyright (C) 2015 Author: Gabriele Paganelli + + 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 Antlr4 input file. + Based on CFtoJLex15.hs + + Author : Gabriele Paganelli (gapag@distruzione.org) + + License : GPL (GNU General Public License) + + Created : 15 Oct, 2015 + + Modified : + + + ************************************************************** +-} + +module BNFC.Backend.Java.CFtoAntlr4Lexer ( cf2AntlrLex ) where + +import Text.PrettyPrint +import BNFC.CF +import BNFC.Backend.Java.RegToAntlrLexer +import BNFC.Backend.Java.Utils +import BNFC.Backend.Common.NamedVariables + +-- | Creates a lexer grammar. +-- Since antlr token identifiers must start with an uppercase symbol, +-- I prepend "Surrogate_id_SYMB_" to the identifier. +-- This introduces risks of clashes if somebody uses the same identifier for +-- user defined tokens. This is not handled. +-- returns the environment because the parser uses it. +cf2AntlrLex :: String -> CF -> (Doc, SymEnv) +cf2AntlrLex packageBase cf = (vcat + [ prelude packageBase + , cMacros + -- unnamed symbols (those in quotes, not in token definitions) + , lexSymbols env + , restOfLexerGrammar cf + ], env) + where + env = makeSymEnv (cfgSymbols cf ++ reservedWords cf) + (0 :: Int) + makeSymEnv [] _ = [] + makeSymEnv (s:symbs) n = (s, "Surrogate_id_SYMB_" ++ show n) + : makeSymEnv symbs (n+1) + + +-- | File prelude +prelude :: String -> Doc +prelude packageBase = vcat + [ "// This Antlr4 file was machine-generated by the BNF converter" + , "lexer grammar" <+> text name <> "Lexer;" + ] + where name = getLastInPackage packageBase + +--For now all categories are included. +--Optimally only the ones that are used should be generated. +cMacros :: Doc +cMacros = vcat + [ "// Predefined regular expressions in BNFC" + , frg "LETTER : CAPITAL | SMALL" + , frg "CAPITAL : [A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE]" + , frg "SMALL : [a-z\\u00DF-\\u00F6\\u00F8-\\u00FF]" + , frg "DIGIT : [0-9]" + ] + where frg a = "fragment" <+> a <+> ";" + +escapeChars :: String -> String +escapeChars = concatMap escapeChar + +-- | +-- >>> lexSymbols [("foo","bar")] +-- bar : 'foo' ; +-- >>> lexSymbols [("\\","bar")] +-- bar : '\\' ; +-- >>> lexSymbols [("/","bar")] +-- bar : '/' ; +-- >>> lexSymbols [("~","bar")] +-- bar : '~' ; +lexSymbols :: SymEnv -> Doc +lexSymbols ss = vcat $ map transSym ss + where + transSym (s,r) = text r <> " : '" <> text (escapeChars s) <> "' ;" + +-- | Writes rules for user defined tokens, and, if used, the predefined BNFC tokens. +restOfLexerGrammar :: CF -> Doc +restOfLexerGrammar cf = vcat + [ lexComments (comments cf) + , "" + , userDefTokens + , ifString strdec + , ifChar chardec + , ifC catDouble [ + "// Double predefined token type", + "DOUBLE : DIGIT+ '.' DIGIT+ ('e' '-'? DIGIT+)?;" + ] + , ifC catInteger [ + "//Integer predefined token type", + "INTEGER : DIGIT+;" + ] + , ifC catIdent [ + "// Identifier token type" , + "fragment" , + "IDENTIFIER_FIRST : LETTER | '_';", + "IDENT : IDENTIFIER_FIRST (IDENTIFIER_FIRST | DIGIT)*;" + ] + , "// Whitespace" + , "WS : (' ' | '\\r' | '\\t' | '\\n')+ -> skip;" + , "// Escapable sequences" + , "fragment" + , "Escapable : ('\"' | '\\\\' | 'n' | 't' | 'r');" + , "ErrorToken : . ;" + , ifString stringmodes + , ifChar charmodes + ] + where + ifC cat s = if isUsedCat cf cat then vcat s else "" + ifString = ifC catString + ifChar = ifC catChar + strdec = [ "// String token type" + , "STRING : '\"' -> more, mode(STRINGMODE);" + ] + chardec = ["CHAR : '\\'' -> more, mode(CHARMODE);"] + userDefTokens = vcat + [ text (show name) <>" : " <> text (printRegJLex exp) <> ";" + | (name, exp) <- tokenPragmas cf ] + stringmodes = [ "mode STRESCAPE;" + , "STRESCAPED : Escapable -> more, popMode ;" + , "mode STRINGMODE;" + , "STRINGESC : '\\\\' -> more , pushMode(STRESCAPE);" + , "STRINGEND : '\"' -> type(STRING), mode(DEFAULT_MODE);" + , "STRINGTEXT : ~[\\\"\\\\] -> more;" + ] + charmodes = [ "mode CHARMODE;" + , "CHARANY : ~[\\'\\\\] -> more, mode(CHAREND);" + , "CHARESC : '\\\\' -> more, pushMode(CHAREND),pushMode(ESCAPE);" + , "mode ESCAPE;" + , "ESCAPED : (Escapable | '\\'') -> more, popMode ;" + , "mode CHAREND;" + , "CHARENDC : '\\'' -> type(CHAR), mode(DEFAULT_MODE);" + ] + +lexComments :: ([(String, String)], [String]) -> Doc +lexComments ([],[]) = "" +lexComments (m,s) = vcat + (prod "COMMENT_antlr_builtin" lexSingleComment s ++ + prod "MULTICOMMENT_antlr_builtin" lexMultiComment m ) + + where + prod bg lc ty = [bg, ": ("] ++ punctuate "|" (map lc ty) ++ skiplex + skiplex = [") -> skip;"] + +-- | Create lexer rule for single-line comments. +-- +-- >>> lexSingleComment "--" +-- '--' ~[\r\n]* (('\r'? '\n')|EOF) +-- +-- >>> lexSingleComment "\"" +-- '"' ~[\r\n]* (('\r'? '\n')|EOF) +lexSingleComment :: String -> Doc +lexSingleComment c = + "'" <>text (escapeChars c) <> "' ~[\\r\\n]* (('\\r'? '\\n')|EOF)" + +-- | Create lexer rule for multi-lines comments. +-- +-- There might be a possible bug here if a language includes 2 multi-line +-- comments. They could possibly start a comment with one character and end it +-- with another. However this seems rare. +-- +-- >>> lexMultiComment ("{-", "-}") +-- '{-' (.)*? '-}' +-- +-- >>> lexMultiComment ("\"'", "'\"") +-- '"\'' (.)*? '\'"' +lexMultiComment :: (String, String) -> Doc +lexMultiComment (b,e) = + "'" <> text (escapeChars b) + <>"' (.)*? '"<> text (escapeChars e) + <> "'" \ No newline at end of file diff --git a/source/src/BNFC/Backend/Java/CFtoAntlr4Parser.hs b/source/src/BNFC/Backend/Java/CFtoAntlr4Parser.hs new file mode 100644 index 00000000..e18fd34a --- /dev/null +++ b/source/src/BNFC/Backend/Java/CFtoAntlr4Parser.hs @@ -0,0 +1,204 @@ +{- + BNF Converter: Antlr4 Java 1.8 Generator + Copyright (C) 2004 Author: Markus Forsberg, Michael Pellauer, + Bjorn Bringert + + 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 ANTLR .g4 input file. It + follows the same basic structure of CFtoHappy. + + Author : Gabriele Paganelli (gapag@distruzione.org), + + + License : GPL (GNU General Public License) + + Created : 15 Oct, 2015 + + Modified : + + + ************************************************************** +-} +module BNFC.Backend.Java.CFtoAntlr4Parser ( cf2AntlrParse ) where + +import Data.List +import BNFC.CF +import BNFC.Backend.Java.Utils +import BNFC.Backend.Common.NamedVariables +import BNFC.Utils ( (+++), (+.+)) + +-- Type declarations +type Rules = [(NonTerminal,[(Pattern, Fun, Action)])] +type Pattern = String +type Action = String +type MetaVar = (String, Cat) + +-- | Creates the ANTLR parser grammar for this CF. +--The environment comes from CFtoAntlr4Lexer +cf2AntlrParse :: String -> String -> CF -> SymEnv -> String +cf2AntlrParse packageBase packageAbsyn cf env = unlines + [ header + , tokens + , prRules packageAbsyn (rulesForAntlr4 packageAbsyn cf env) + ] + where + header :: String + header = unlines + [ "// -*- Java -*- This ANTLRv4 file was machine-generated by BNFC" + , "parser grammar" +++ identifier ++ "Parser;" + ] + tokens :: String + tokens = unlines + [ "options {" + , " tokenVocab = "++identifier++"Lexer;" + , "}" + ] + identifier = getLastInPackage packageBase + +--The following functions are a (relatively) straightforward translation +--of the ones in CFtoHappy.hs +rulesForAntlr4 :: String -> CF -> SymEnv -> Rules +rulesForAntlr4 packageAbsyn cf env = map mkOne getrules + where + getrules = ruleGroups cf + mkOne (cat,rules) = constructRule packageAbsyn cf env rules cat + +-- | For every non-terminal, we construct a set of rules. A rule is a sequence of +-- terminals and non-terminals, and an action to be performed. +constructRule :: String -> CF -> SymEnv -> [Rule] -> NonTerminal -> (NonTerminal,[(Pattern, Fun, Action)]) +constructRule packageAbsyn cf env rules nt = + (nt, [ (p , funRule r , generateAction packageAbsyn nt (funRule r) (revM b m) b) + | (index ,r0) <- zip [1..(length rules)] rules, + let (b,r) = if isConsFun (funRule r0) && elem (valCat r0) revs + then (True, revSepListRule r0) + else (False, r0) + (p,m) = generatePatterns index env r]) + where + revM False = id + revM True = reverse + revs = cfgReversibleCats cf + +-- Generates a string containing the semantic action. +generateAction :: String -> NonTerminal -> Fun -> [MetaVar] + -> Bool -- ^ Whether the list should be reversed or not. + -- Only used if this is a list rule. + -> Action +generateAction packageAbsyn nt f ms rev + | isNilFun f = "$result = new " ++ c ++ "();" + | isOneFun f = "$result = new " ++ c ++ "(); $result.addLast(" + ++ p_1 ++ ");" + | isConsFun f = "$result = " ++ p_2 ++ "; " + ++ "$result." ++ add ++ "(" ++ p_1 ++ ");" + | isCoercion f = "$result = " ++ p_1 ++ ";" + | isDefinedRule f = "$result = parser." ++ f ++ "_" + ++ "(" ++ intercalate "," (map resultvalue ms) ++ ");" + | otherwise = "$result = new " ++ c + ++ "(" ++ intercalate "," (map resultvalue ms) ++ ");" + where + c = packageAbsyn ++ "." ++ + if isNilFun f || isOneFun f || isConsFun f + then identCat (normCat nt) else f + p_1 = resultvalue $ ms!!0 + p_2 = resultvalue $ ms!!1 + add = if rev then "addLast" else "addFirst" + gettext = "getText()" + removeQuotes x = "substring(1, "++ x +.+ gettext +.+ "length()-1)" + parseint x = "Integer.parseInt("++x++")" + parsedouble x = "Double.parseDouble("++x++")" + charat = "charAt(1)" + resultvalue (n,c) = case c of + TokenCat "Ident" -> n'+.+gettext + TokenCat "Integer" -> parseint $ n'+.+gettext + TokenCat "Char" -> n'+.+gettext+.+charat + TokenCat "Double" -> parsedouble $ n'+.+gettext + TokenCat "String" -> n'+.+gettext+.+removeQuotes n' + _ -> (+.+) n' (if isTokenCat c then gettext else "result") + where n' = '$':n + +-- | Generate patterns and a set of metavariables indicating +-- where in the pattern the non-terminal +-- >>> generatePatterns 2 [] (Rule "myfun" (Cat "A") []) +-- (" /* empty */ ",[]) +-- >>> generatePatterns 3 [("def", "_SYMB_1")] (Rule "myfun" (Cat "A") [Right "def", Left (Cat "B")]) +-- ("_SYMB_1 p_3_2=b ",[("p_3_2",B)]) +generatePatterns :: Int -> SymEnv -> Rule -> (Pattern,[MetaVar]) +generatePatterns ind env r = case rhsRule r of + [] -> (" /* empty */ ",[]) + its -> (mkIt 1 its, metas its) + where + mkIt _ [] = [] + mkIt n (i:is) = case i of + Left c -> "p_" ++show ind++"_"++ show (n :: Int) ++ "="++ c' + +++ mkIt (n+1) is + where + c' = case c of + TokenCat "Ident" -> "IDENT" + TokenCat "Integer" -> "INTEGER" + TokenCat "Char" -> "CHAR" + TokenCat "Double" -> "DOUBLE" + TokenCat "String" -> "STRING" + _ -> if isTokenCat c + then identCat c + else firstLowerCase + (getRuleName (identCat c)) + Right s -> case lookup s env of + (Just x) -> x +++ mkIt (n+1) is + (Nothing) -> mkIt n is + metas its = [("p_" ++ show ind ++"_"++ show i, category) + | (i,Left category) <- zip [1 :: Int ..] its] + +-- | Puts together the pattern and actions and returns a string containing all +-- the rules. +prRules :: String -> Rules -> String +prRules _ [] = [] +prRules packabs ((_, []):rs) = prRules packabs rs +prRules packabs ((nt,(p, fun, a):ls):rs) = + preamble ++ ";\n" ++ prRules packabs rs + where + preamble = unwords [ nt' + , "returns" + , "[" + , packabs+.+normcat + , "result" + , "]" + , ":" + , p + , "{" + , a + , "}" + , "#" + , antlrRuleLabel fun + , '\n' : pr ls + ] + alternative (p',fun',a') + = unwords [" |", p', "{", a' , "}", "#" + , antlrRuleLabel fun'] + catid = identCat nt + normcat = identCat (normCat nt) + nt' = getRuleName $ firstLowerCase catid + pr [] = [] + pr (k:ls) = unlines [alternative k] ++ pr ls + antlrRuleLabel fnc + | isNilFun fnc = catid ++ "_Empty" + | isOneFun fnc = catid ++ "_AppendLast" + | isConsFun fnc = catid ++ "_PrependFirst" + | isCoercion fnc = "Coercion_" ++ catid + | otherwise = getLabelName fnc \ No newline at end of file diff --git a/source/src/BNFC/Backend/Java/CFtoComposVisitor.hs b/source/src/BNFC/Backend/Java/CFtoComposVisitor.hs index 3e1aba6f..4f2d85a3 100644 --- a/source/src/BNFC/Backend/Java/CFtoComposVisitor.hs +++ b/source/src/BNFC/Backend/Java/CFtoComposVisitor.hs @@ -20,12 +20,12 @@ module BNFC.Backend.Java.CFtoComposVisitor (cf2ComposVisitor) where +import Data.List +import Data.Either (lefts) import BNFC.CF import BNFC.Backend.Java.CFtoJavaAbs15 (typename) import BNFC.Utils ((+++)) import BNFC.Backend.Common.NamedVariables -import Data.List -import Data.Either (lefts) import BNFC.PrettyPrint cf2ComposVisitor :: String -> String -> CF -> String @@ -35,18 +35,19 @@ cf2ComposVisitor packageBase packageAbsyn cf = concatMap (prData packageAbsyn user) groups, "}"] where - user = fst (unzip (tokenPragmas cf)) - groups = [ g | g@(c,_) <- fixCoercions (ruleGroupsInternals cf), not (isList c) ] - is = map (prInterface packageAbsyn) groups + user = fst (unzip (tokenPragmas cf)) + groups = [ g + | g@(c,_) <- fixCoercions (ruleGroupsInternals cf), not (isList c) ] + is = map (prInterface packageAbsyn) groups header = unlines [ - "package" +++ packageBase ++ ";", - "import" +++ packageAbsyn ++ ".*;", - "/** BNFC-Generated Composition Visitor", - "*/", - "", - "public class ComposVisitor implements", - intercalate ",\n" $ map (" "++) is, - "{" + "package" +++ packageBase ++ ";" + , "import" +++ packageAbsyn ++ ".*;" + , "/** BNFC-Generated Composition Visitor" + , "*/" + , "" + , "public class ComposVisitor implements" + , intercalate ",\n" $ map (" "++) is + , "{" ] @@ -109,12 +110,13 @@ prCat user (cat, nt) $$ codeblock 2 [ nt <> ".add(x.accept(this,arg));" ] | otherwise = decl (var <> ".accept(this, arg)") where - var = "p." <> nt - varType = typename (identCat (normCat cat)) user - et = typename (show$normCatOfList cat) user - decl v = text varType <+> nt <+> "=" <+> v <> ";" + var = "p." <> nt + varType = typename (identCat (normCat cat)) user + et = typename (show$normCatOfList cat) user + decl v = text varType <+> nt <+> "=" <+> v <> ";" --Just checks if something is a basic or user-defined type. isBasicType :: [UserDef] -> String -> Bool -isBasicType user v = v `elem` (map show user ++ ["Integer","Character","String","Double"]) +isBasicType user v = + v `elem` (map show user ++ ["Integer","Character","String","Double"]) diff --git a/source/src/BNFC/Backend/Java/CFtoCup15.hs b/source/src/BNFC/Backend/Java/CFtoCup15.hs index 7d89f629..17e6fc96 100644 --- a/source/src/BNFC/Backend/Java/CFtoCup15.hs +++ b/source/src/BNFC/Backend/Java/CFtoCup15.hs @@ -70,25 +70,27 @@ cf2Cup packageBase packageAbsyn cf env = unlines where header :: String header = unlines - ["// -*- Java -*- This Cup file was machine-generated by BNFC", - "package" +++ packageBase ++ ";", - "", - "parser code {:", - parseMethod packageAbsyn (firstEntry cf), - "public > A cons_(B x, A xs) { xs.addFirst(x); return xs; }", - definedRules packageAbsyn cf, - -- unlines $ map (parseMethod packageAbsyn) (allEntryPoints cf), - "public void syntax_error(java_cup.runtime.Symbol cur_token)", - "{", - "\treport_error(\"Syntax Error, trying to recover and continue parse...\", cur_token);", - "}", - "", - "public void unrecovered_syntax_error(java_cup.runtime.Symbol cur_token) throws java.lang.Exception", - "{", - "\tthrow new Exception(\"Unrecoverable Syntax Error\");", - "}", - "", - ":}" + ["// -*- Java -*- This Cup file was machine-generated by BNFC" + , "package" +++ packageBase ++ ";" + , "" + , "parser code {:" + , parseMethod packageAbsyn (firstEntry cf) + , "public > " + ++ "A cons_(B x, A xs) { xs.addFirst(x); return xs; }" + , definedRules packageAbsyn cf + , "public void syntax_error(java_cup.runtime.Symbol cur_token)" + , "{" + , "\treport_error(\"Syntax Error, trying to recover and continue" + ++ " parse...\", cur_token);" + , "}" + , "" + , "public void unrecovered_syntax_error(java_cup.runtime.Symbol " + ++ "cur_token) throws java.lang.Exception" + , "{" + , "\tthrow new Exception(\"Unrecoverable Syntax Error\");" + , "}" + , "" + , ":}" ] definedRules :: String -> CF -> String @@ -104,7 +106,9 @@ definedRules packageAbsyn cf = rule f xs e = case checkDefinition' list ctx f xs e of - Bad err -> error $ "Panic! This should have been caught already:\n" ++ err + Bad err -> + error $ "Panic! This should have been caught already:\n" + ++ err Ok (args,(e',t)) -> unlines [ "public " ++ javaType t ++ " " ++ f ++ "_ (" ++ intercalate ", " (map javaArg args) ++ ") {" @@ -114,11 +118,13 @@ definedRules packageAbsyn cf = where javaType :: Base -> String - javaType (ListT (BaseT x)) = packageAbsyn ++ ".List" ++ show (normCat$strToCat x) + javaType (ListT (BaseT x)) = packageAbsyn ++ ".List" + ++ show (normCat$strToCat x) javaType (ListT t) = javaType t javaType (BaseT x) | isToken x ctx = "String" - | otherwise = packageAbsyn ++ "." ++ show (normCat$strToCat x) + | otherwise = packageAbsyn ++ "." + ++ show (normCat$strToCat x) javaArg :: (String, Base) -> String javaArg (x,t) = javaType t ++ " " ++ x ++ "_" @@ -130,7 +136,8 @@ definedRules packageAbsyn cf = javaExp (App t [e]) | isToken t ctx = call "new String" [e] javaExp (App x es) - | isUpper (head x) = call ("new " ++ packageAbsyn ++ "." ++ x) es + | isUpper (head x) = call + ("new " ++ packageAbsyn ++ "." ++ x) es | otherwise = call (x ++ "_") es javaExp (LitInt n) = "new Integer(" ++ show n ++ ")" javaExp (LitDouble x) = "new Double(" ++ show x ++ ")" @@ -177,17 +184,15 @@ tokens ts = unlines (map declTok ts) specialToks :: CF -> String specialToks cf = unlines [ - ifC catString "terminal String _STRING_;", - ifC catChar "terminal Character _CHAR_;", - ifC catInteger "terminal Integer _INTEGER_;", - ifC catDouble "terminal Double _DOUBLE_;", - ifC catIdent "terminal String _IDENT_;" + ifC catString "terminal String _STRING_;" + , ifC catChar "terminal Character _CHAR_;" + , ifC catInteger "terminal Integer _INTEGER_;" + , ifC catDouble "terminal Double _DOUBLE_;" + , ifC catIdent "terminal String _IDENT_;" ] where ifC cat s = if isUsedCat cf cat then s else "" --- This handles user defined tokens --- FIXME specialRules:: CF -> String specialRules cf = unlines ["terminal String " ++ name ++ ";" | name <- tokenNames cf] @@ -200,7 +205,8 @@ rulesForCup packageAbsyn cf env = map mkOne $ ruleGroups cf where -- | For every non-terminal, we construct a set of rules. A rule is a sequence of -- terminals and non-terminals, and an action to be performed. -constructRule :: String -> CF -> SymEnv -> [Rule] -> NonTerminal -> (NonTerminal,[(Pattern,Action)]) +constructRule :: String -> CF -> SymEnv -> [Rule] -> NonTerminal + -> (NonTerminal,[(Pattern,Action)]) constructRule packageAbsyn cf env rules nt = (nt, [ (p, generateAction packageAbsyn nt (funRule r) (revM b m) b) | r0 <- rules, @@ -210,8 +216,8 @@ constructRule packageAbsyn cf env rules nt = (p,m) = generatePatterns env r]) where revM False = id - revM True = reverse - revs = cfgReversibleCats cf + revM True = reverse + revs = cfgReversibleCats cf -- Generates a string containing the semantic action. generateAction :: String -> NonTerminal -> Fun -> [MetaVar] @@ -219,19 +225,20 @@ generateAction :: String -> NonTerminal -> Fun -> [MetaVar] -- Only used if this is a list rule. -> Action generateAction packageAbsyn nt f ms rev - | isNilFun f = "RESULT = new " ++ c ++ "();" - | isOneFun f = "RESULT = new " ++ c ++ "(); RESULT.addLast(" ++ p_1 ++ ");" - | isConsFun f = "RESULT = " ++ p_2 ++ "; " + | isNilFun f = "RESULT = new " ++ c ++ "();" + | isOneFun f = "RESULT = new " ++ c ++ "(); RESULT.addLast(" + ++ p_1 ++ ");" + | isConsFun f = "RESULT = " ++ p_2 ++ "; " ++ p_2 ++ "." ++ add ++ "(" ++ p_1 ++ ");" - | isCoercion f = "RESULT = " ++ p_1 ++ ";" + | isCoercion f = "RESULT = " ++ p_1 ++ ";" | isDefinedRule f = "RESULT = parser." ++ f ++ "_" ++ "(" ++ intercalate "," ms ++ ");" - | otherwise = "RESULT = new " ++ c + | otherwise = "RESULT = new " ++ c ++ "(" ++ intercalate "," ms ++ ");" where - c = packageAbsyn ++ "." ++ + c = packageAbsyn ++ "." ++ if isNilFun f || isOneFun f || isConsFun f - then identCat (normCat nt) else f + then identCat (normCat nt) else f p_1 = ms!!0 p_2 = ms!!1 add = if rev then "addLast" else "addFirst" diff --git a/source/src/BNFC/Backend/Java/CFtoJLex15.hs b/source/src/BNFC/Backend/Java/CFtoJLex15.hs index 5301c93f..530c8bee 100644 --- a/source/src/BNFC/Backend/Java/CFtoJLex15.hs +++ b/source/src/BNFC/Backend/Java/CFtoJLex15.hs @@ -46,8 +46,8 @@ import BNFC.Backend.Common.NamedVariables import Text.PrettyPrint --The environment must be returned for the parser to use. -cf2jlex :: String -> CF -> Bool -> (Doc, SymEnv) -cf2jlex packageBase cf jflex = (vcat +cf2jlex :: Bool -> String -> CF -> (Doc, SymEnv) +cf2jlex jflex packageBase cf = (vcat [ prelude jflex packageBase, cMacros, @@ -59,7 +59,6 @@ cf2jlex packageBase cf jflex = (vcat makeSymEnv [] _ = [] makeSymEnv (s:symbs) n = (s, "_SYMB_" ++ show n) : makeSymEnv symbs (n+1) - -- | File prelude prelude :: Bool -> String -> Doc prelude jflex packageBase = vcat diff --git a/source/src/BNFC/Backend/Java/CFtoJavaPrinter15.hs b/source/src/BNFC/Backend/Java/CFtoJavaPrinter15.hs index d4c29871..8d56d6b9 100644 --- a/source/src/BNFC/Backend/Java/CFtoJavaPrinter15.hs +++ b/source/src/BNFC/Backend/Java/CFtoJavaPrinter15.hs @@ -213,22 +213,25 @@ prEntryPoints packageAbsyn cf = prEntryPoint _ = "" prData :: String -> [UserDef] -> (Cat, [Rule]) -> String -prData packageAbsyn user (cat, rules) = - if isList cat - then unlines - [ - " private static void pp(" ++ packageAbsyn ++ "." - ++ identCat (normCat cat) +++ "foo, int _i_)", - " {", - render $ nest 5 $ prList user cat rules <> " }" - ] - else unlines --not a list - [ - " private static void pp(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo, int _i_)", - " {", - concat (addElse $ map (prRule packageAbsyn) rules) ++ " }" - ] - where addElse = map (" "++). intersperse "else " . filter (not . null) . map (dropWhile isSpace) +prData packageAbsyn user (cat, rules) = unlines k + where + k = if isList cat + then + [" private static void pp(" ++ packageAbsyn ++ "." + ++ identCat (normCat cat) +++ "foo, int _i_)" + , " {" + , render $ nest 5 $ prList user cat rules <> " }" + ] + else --not a list + [ + " private static void pp(" ++ packageAbsyn ++ "." + ++ identCat (normCat cat) +++ "foo, int _i_)", + " {", + concat (addElse $ map (prRule packageAbsyn) rules) ++ " }" + ] + addElse = map (" "++). intersperse "else " . filter (not . null) + . map (dropWhile isSpace) + prRule :: String -> Rule -> String prRule packageAbsyn r@(Rule fun _c cats) | not (isCoercion fun || isDefinedRule fun) = concat @@ -288,7 +291,7 @@ prList user c rules = -- >>> prCat "F" (Right "++") -- render("++"); -- --- >>> prCat "F" (Left (Cat "String", "string_")) +-- >>> prCat "F" (Left (TokenCat "String", "string_")) -- printQuoted(F.string_); -- -- >>> prCat "F" (Left (InternalCat, "#_")) @@ -298,7 +301,7 @@ prList user c rules = -- prCat :: Doc -> Either (Cat, Doc) String -> Doc prCat _ (Right t) = nest 7 ("render(\"" <> text(escapeChars t) <> "\");\n") -prCat fnm (Left (Cat "String", nt)) +prCat fnm (Left (TokenCat "String", nt)) = nest 7 ("printQuoted(" <> fnm <> "." <> nt <> ");\n") prCat _ (Left (InternalCat, _)) = empty prCat fnm (Left (cat, nt)) @@ -307,20 +310,22 @@ prCat fnm (Left (cat, nt)) --The following methods generate the Show function. shData :: String -> [UserDef] -> (Cat, [Rule]) -> String -shData packageAbsyn user (cat, rules) = - if isList cat - then unlines - [ - " private static void sh(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo)", - " {", - shList user cat rules ++ " }" - ] - else unlines - [ - " private static void sh(" ++ packageAbsyn ++ "." ++ identCat (normCat cat) +++ "foo)", - " {", - concatMap (shRule packageAbsyn) rules ++ " }" - ] +shData packageAbsyn user (cat, rules) = unlines k + where + k = if isList cat + then + [ " private static void sh(" ++ packageAbsyn ++ "." + ++ identCat (normCat cat) +++ "foo)" + , " {" + , shList user cat rules ++ " }" + ] + else + [ " private static void sh(" ++ packageAbsyn ++ "." + ++ identCat (normCat cat) +++ "foo)" + , " {" + , concatMap (shRule packageAbsyn) rules ++ " }" + ] + shRule :: String -> Rule -> String shRule packageAbsyn (Rule fun _c cats) | not (isCoercion fun || isDefinedRule fun) = unlines diff --git a/source/src/BNFC/Backend/Java/RegToAntlrLexer.hs b/source/src/BNFC/Backend/Java/RegToAntlrLexer.hs new file mode 100644 index 00000000..97b2d9da --- /dev/null +++ b/source/src/BNFC/Backend/Java/RegToAntlrLexer.hs @@ -0,0 +1,81 @@ +module BNFC.Backend.Java.RegToAntlrLexer (printRegJLex, escapeChar) where + +-- modified from RegToJLex.hs + +import AbsBNF + +-- the top-level printing method +printRegJLex :: Reg -> String +printRegJLex = render . prt 0 + +-- you may want to change render and parenth + +render :: [String] -> String +render = rend (0 :: Int) where + rend i ss = case ss of + "[" :ts -> cons "[" $ rend i ts + "(" :ts -> cons "(" $ rend i ts + t : "," :ts -> cons t $ space "," $ rend i ts + t : ")" :ts -> cons t $ cons ")" $ rend i ts + t : "]" :ts -> cons t $ cons "]" $ rend i ts + t :ts -> space t $ rend i ts + _ -> "" + cons s t = s ++ t + space t s = if null s then t else t ++ s + +parenth :: [String] -> [String] +parenth ss = ["("] ++ ss ++ [")"] + +-- the printer class does the job +class Print a where + prt :: Int -> a -> [String] + prtList :: [a] -> [String] + prtList = concatMap (prt 0) + +instance Print a => Print [a] where + prt _ = prtList + +instance Print Char where + prt _ c = [escapeChar c] + prtList = map (concat . prt 0) + +escapeChar :: Char -> String +escapeChar x | x `elem` reserved = '\\' : [x] +escapeChar x = [x] + +-- Characters that must be escaped in ANTLR regular expressions +reserved :: [Char] +reserved = ['\'','\\'] + +prPrec :: Int -> Int -> [String] -> [String] +prPrec i j = if j prPrec i 2 (concat [prt 2 reg0 , prt 3 reg]) + RAlt reg0 reg + -> prPrec i 1 (concat [prt 1 reg0 , ["|"] , prt 2 reg]) + -- JLex does not support set difference + --RMinus reg0 reg -> prPrec i 1 (concat [prt 2 reg0 , ["#"] , prt 2 reg]) + RMinus reg0 REps -> prt i reg0 -- REps is identity for set difference + RMinus RAny reg@(RChar _) + -> prPrec i 3 (concat [["~["],prt 0 reg,["]"]]) + RMinus RAny (RAlts str) + -> prPrec i 3 (concat [["~["],prt 0 str,["]"]]) + RMinus _ _ -> error "Antlr does not support general set difference" + RStar reg -> prPrec i 3 (concat [prt 3 reg , ["*"]]) + RPlus reg -> prPrec i 3 (concat [prt 3 reg , ["+"]]) + ROpt reg -> prPrec i 3 (concat [prt 3 reg , ["?"]]) + REps -> prPrec i 3 [""] + RChar c -> prPrec i 3 (concat [["'"], prt 0 c, ["'"]]) + RAlts str -> prPrec i 3 (concat [["["],prt 0 str,["]"]]) + RSeqs str -> prPrec i 2 (concatMap (prt 0) str) + RDigit -> prPrec i 3 ["DIGIT"] + RLetter -> prPrec i 3 ["LETTER"] + RUpper -> prPrec i 3 ["CAPITAL"] + RLower -> prPrec i 3 ["SMALL"] + RAny -> prPrec i 3 ["[\\u0000-\\u00FF]"] diff --git a/source/src/BNFC/Backend/Java/RegToJLex.hs b/source/src/BNFC/Backend/Java/RegToJLex.hs index e44436e6..1a1be254 100644 --- a/source/src/BNFC/Backend/Java/RegToJLex.hs +++ b/source/src/BNFC/Backend/Java/RegToJLex.hs @@ -30,14 +30,14 @@ parenth ss = ["("] ++ ss ++ [")"] class Print a where prt :: Int -> a -> [String] prtList :: [a] -> [String] - prtList = concat . map (prt 0) + prtList = concatMap (prt 0) instance Print a => Print [a] where prt _ = prtList instance Print Char where prt _ c = [escapeChar False c] - prtList s = map (concat . prt 0) s + prtList = map (concat . prt 0) escapeChar :: Bool -> Char -> String escapeChar _ '^' = "\\x5E" -- special case, since \^ is a control character escape @@ -52,6 +52,7 @@ jlexReserved = ['?','*','+','|','(',')','^','$','.','[',']','{','}','"','\\'] jflexReserved :: [Char] jflexReserved = '~':'!':'/':jlexReserved + prPrec :: Int -> Int -> [String] -> [String] prPrec i j = if j prPrec i 3 (concat [["[^"],prt 0 reg,["]"]]) RMinus RAny (RAlts str) -> prPrec i 3 (concat [["[^"],prt 0 str,["]"]]) -- FIXME: maybe we could add cases for char - RDigit, RLetter etc. - RMinus _ _ -> error $ "JLex does not support general set difference" + RMinus _ _ -> error "JLex does not support general set difference" RStar reg -> prPrec i 3 (concat [prt 3 reg , ["*"]]) RPlus reg -> prPrec i 3 (concat [prt 3 reg , ["+"]]) ROpt reg -> prPrec i 3 (concat [prt 3 reg , ["?"]]) - REps -> prPrec i 3 (["[^.]"]) - RChar c -> prPrec i 3 (concat [prt 0 c]) + REps -> prPrec i 3 ["[^.]"] + RChar c -> prPrec i 3 (prt 0 c) RAlts str -> prPrec i 3 (concat [["["],prt 0 str,["]"]]) - RSeqs str -> prPrec i 2 (concat (map (prt 0) str)) - RDigit -> prPrec i 3 (concat [["{DIGIT}"]]) - RLetter -> prPrec i 3 (concat [["{LETTER}"]]) - RUpper -> prPrec i 3 (concat [["{CAPITAL}"]]) - RLower -> prPrec i 3 (concat [["{SMALL}"]]) - RAny -> prPrec i 3 (concat [["."]]) + RSeqs str -> prPrec i 2 (concatMap (prt 0) str) + RDigit -> prPrec i 3 ["{DIGIT}"] + RLetter -> prPrec i 3 ["{LETTER}"] + RUpper -> prPrec i 3 ["{CAPITAL}"] + RLower -> prPrec i 3 ["{SMALL}"] + RAny -> prPrec i 3 ["."] diff --git a/source/src/BNFC/Backend/Java/Utils.hs b/source/src/BNFC/Backend/Java/Utils.hs new file mode 100644 index 00000000..48538584 --- /dev/null +++ b/source/src/BNFC/Backend/Java/Utils.hs @@ -0,0 +1,25 @@ +module BNFC.Backend.Java.Utils where +import BNFC.Backend.Common.NamedVariables +import BNFC.Utils ( mkName, NameStyle(..)) + +javaReserved = [ + "abstract" ,"continue" ,"for" ,"new" ,"switch" + ,"assert" ,"default" ,"goto" ,"package" ,"synchronized" + ,"boolean" ,"do" ,"if" ,"private" ,"this" + ,"break" ,"double" ,"implements" ,"protected" ,"throw" + ,"byte" ,"else" ,"import" ,"public" ,"throws" + ,"case" ,"enum" ,"instanceof" ,"return" ,"transient" + ,"catch" ,"extends" ,"int" ,"short" ,"try" + ,"char" ,"final" ,"interface" ,"static" ,"void" + ,"class" ,"finally" ,"long" ,"strictfp" ,"volatile" + ,"const" ,"float" ,"native" ,"super" ,"while" + ] + +getRuleName z = if x `elem` ("grammar" : javaReserved) then z ++ "_" else z + where x = firstLowerCase z + +getLabelName = mkName ["Rule"] CamelCase + +getLastInPackage :: String -> String +getLastInPackage = + last . words . map (\c -> if c == '.' then ' ' else c) \ No newline at end of file diff --git a/source/src/BNFC/Options.hs b/source/src/BNFC/Options.hs index 35cd098f..c21ddc39 100644 --- a/source/src/BNFC/Options.hs +++ b/source/src/BNFC/Options.hs @@ -55,6 +55,8 @@ data AlexVersion = Alex1 | Alex2 | Alex3 data HappyMode = Standard | GLR deriving (Eq,Show,Bounded,Enum,Ord) +data JavaLexerParser = JLexCup | JFlexCup | Antlr4 + deriving (Eq,Show,Ord) -- | This is the option record that is passed to the different backends data SharedOptions = Options -- Option shared by at least 2 backends @@ -67,7 +69,7 @@ data SharedOptions = Options , lang :: String -- Haskell specific: , alexMode :: AlexVersion - , jflex :: Bool + , javaLexerParser :: JavaLexerParser , inDir :: Bool , shareStrings :: Bool , byteStrings :: Bool @@ -107,7 +109,7 @@ defaultOptions = Options , wcf = False , functor = False , outDir = "." - , jflex = False + , javaLexerParser = JLexCup } -- ~~~ Option definition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -122,7 +124,7 @@ globalOptions = [ targetOptions :: [ OptDescr (SharedOptions -> SharedOptions)] targetOptions = [ Option "" ["java"] (NoArg (\o -> o {target = TargetJava})) - "Output Java code for use with JLex and CUP" + "Output Java code [default: for use with JLex and CUP]" , Option "" ["haskell"] (NoArg (\o -> o {target = TargetHaskell})) "Output Haskell code for use with Alex and Happy (default)" , Option "" ["haskell-gadt"] (NoArg (\o -> o {target = TargetHaskellGadt})) @@ -151,14 +153,20 @@ specificOptions :: [(OptDescr (SharedOptions -> SharedOptions), [Target])] specificOptions = [ ( Option ['l'] [] (NoArg (\o -> o {linenumbers = True})) "Add and set line_number field for all syntax classes" - , [TargetCpp] ) + , [TargetCpp, TargetJava] ) , ( Option ['p'] [] (ReqArg (\n o -> o {inPackage = Just n}) "") "Prepend to the package/module name" , [TargetCpp, TargetCSharp, TargetHaskell, TargetHaskellGadt, TargetProfile, TargetJava] ) - , ( Option [] ["jflex"] (NoArg (\o -> o {jflex = True})) - "Use JFlex instead of JLex for lexing" + , ( Option [] ["jflex"] (NoArg (\o -> o {javaLexerParser = JFlexCup})) + "Lex with JFlex, parse with CUP" , [TargetJava] ) + , ( Option [] ["jlex"] (NoArg (\o -> o {javaLexerParser = Antlr4})) + "Lex with Jlex, parse with CUP (default)" + , [TargetJava] ) + , ( Option [] ["antlr4"] (NoArg (\o -> o {javaLexerParser = Antlr4})) + "Lex and parse with antlr4" + , [TargetJava] ) , ( Option [] ["vs"] (NoArg (\o -> o {visualStudio = True})) "Generate Visual Studio solution/project files" , [TargetCSharp] ) diff --git a/source/src/BNFC/Utils.hs b/source/src/BNFC/Utils.hs index d38832f4..3afa2e20 100644 --- a/source/src/BNFC/Utils.hs +++ b/source/src/BNFC/Utils.hs @@ -18,7 +18,7 @@ -} module BNFC.Utils - ( (+++), (++++) + ( (+++), (++++), (+-+), (+.+) , mkName, mkNames, NameStyle(..) , lowerCase, upperCase, mixedCase, camelCase, snakeCase , replace, prParenth @@ -40,9 +40,11 @@ infixr 5 ++++ -- printing operations -(+++), (++++) :: String -> String -> String +(+.+), (+++), (++++), (+-+) :: String -> String -> String a +++ b = a ++ " " ++ b a ++++ b = a ++ "\n" ++ b +a +-+ b = a ++ "_" ++ b +a +.+ b = a ++ "." ++ b prParenth :: String -> String prParenth s = if s == "" then "" else "(" ++ s ++ ")" diff --git a/source/test/BNFC/Backend/JavaSpec.hs b/source/test/BNFC/Backend/JavaSpec.hs index 6c72bbcb..add8db65 100644 --- a/source/test/BNFC/Backend/JavaSpec.hs +++ b/source/test/BNFC/Backend/JavaSpec.hs @@ -5,7 +5,6 @@ import BNFC.GetCF import Test.Hspec import BNFC.Hspec - import BNFC.Backend.Java -- SUT calcOptions = defaultOptions { lang = "Calc" } @@ -17,10 +16,9 @@ getCalc = parseCF calcOptions TargetJava $ , "EInt. Exp2 ::= Integer ;" , "coercions Exp 2 ;" ] -spec = - describe "C backend" $ +spec = do + describe "Java backend" $ it "respect the makefile option" $ do calc <- getCalc let opts = calcOptions { make = Just "MyMakefile" } - makeJava opts calc `shouldGenerate` "MyMakefile" - + makeJava opts calc `shouldGenerate` "MyMakefile" \ No newline at end of file diff --git a/testing/data/javatools.jar b/testing/data/javatools.jar index 44a42643..681c0ec8 100644 Binary files a/testing/data/javatools.jar and b/testing/data/javatools.jar differ diff --git a/testing/src/ParameterizedTests.hs b/testing/src/ParameterizedTests.hs index 6f0b8b68..8dd0016b 100644 --- a/testing/src/ParameterizedTests.hs +++ b/testing/src/ParameterizedTests.hs @@ -133,7 +133,8 @@ data TestParameters = TP parameters :: [TestParameters] parameters = - [ hsParams { tpName = "Haskell" , tpBnfcOptions = ["--haskell", "-m"] } + [ + hsParams { tpName = "Haskell" , tpBnfcOptions = ["--haskell", "-m"] } , hsParams { tpName = "Haskell (with functor)" , tpBnfcOptions = ["--haskell", "--functor", "-m"] } , hsParams { tpName = "Haskell (with namespace)" @@ -158,12 +159,16 @@ parameters = , tpBnfcOptions = ["--cpp", "-m"] } , base { tpName = "C++ (no STL)" , tpBnfcOptions = ["--cpp-nostl", "-m"] } - , javaParams { tpName = "Java" + , + javaParams { tpName = "Java" , tpBnfcOptions = ["--java", "-m"] } + , javaParams { tpName = "Java (with namespace)" , tpBnfcOptions = ["--java", "-p","my.stuff", "-m"] } , javaParams { tpName = "Java (with jflex)" , tpBnfcOptions = ["--java", "--jflex", "-m"] } + , javaParams { tpName = "Java (with antlr)" + , tpBnfcOptions = ["--java", "--antlr", "-m"] } ] where base = TP