BNFC-2.8.3: src/BNFC/Backend/CSharp.hs
{-
BNF Converter: C# Main file
Copyright (C) 2006-2007 Author: Johan Broberg
Modified from STLTop 2006.
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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
-}
{-
**************************************************************
BNF Converter Module
Description : C# Main file
Author : Johan Broberg (johan@pontemonti.com)
License : GPL (GNU General Public License)
Created : 20 November, 2006
Modified : 8 January, 2007 by Johan Broberg
**************************************************************
-}
module BNFC.Backend.CSharp (makeCSharp) where
import BNFC.CF
import BNFC.Options
import BNFC.Backend.Common.OOAbstract
import BNFC.Backend.Common.Makefile
import BNFC.Backend.Base
import BNFC.Backend.CSharp.CAbstoCSharpAbs
import BNFC.Backend.CSharp.CFtoGPLEX
import BNFC.Backend.CSharp.CFtoGPPG
import BNFC.Backend.CSharp.CAbstoCSharpVisitSkeleton
import BNFC.Backend.CSharp.CAbstoCSharpAbstractVisitSkeleton
import BNFC.Backend.CSharp.CFtoCSharpPrinter
import BNFC.Backend.CSharp.CSharpUtils
import BNFC.PrettyPrint hiding ((<.>), (<>))
import System.Environment (getEnv)
import System.FilePath ((<.>))
import System.Directory
import System.IO
import System.IO.Error (catchIOError)
import System.Process
import Data.Maybe
import Control.Monad (when)
import qualified BNFC.Backend.Common.Makefile as Makefile
-- Control.Monad.State
makeCSharp :: SharedOptions -> CF -> MkFiles ()
makeCSharp opts cf = do
let namespace = fromMaybe (lang opts) maybenamespace
cabs = cf2cabs cf
absyn = cabs2csharpabs namespace cabs wcfSupport
(gplex, env) = cf2gplex namespace cf
gppg = cf2gppg namespace cf env
skeleton = cabs2csharpvisitskeleton namespace cabs
absSkeleton = cabs2csharpAbstractVisitSkeleton namespace cabs
printer = cf2csharpprinter namespace cf
mkfile "Absyn.cs" absyn
mkfile (namespace ++ ".l") gplex
liftIO $ putStrLn " (Tested with GPLEX RC1)"
mkfile (namespace ++ ".y") gppg
liftIO $ putStrLn " (Tested with GPPG 1.0)"
mkfile "AbstractVisitSkeleton.cs" absSkeleton
mkfile "VisitSkeleton.cs" skeleton
mkfile "Printer.cs" printer
mkfile "Test.cs" (csharptest namespace cf)
when vsfiles (writeVisualStudioFiles namespace)
when makefile (writeMakefile opts namespace)
where makefile = isJust $ make opts
vsfiles = visualStudio opts
wcfSupport = wcf opts
maybenamespace = inPackage opts
writeMakefile :: SharedOptions -> Namespace -> MkFiles ()
writeMakefile opts namespace = do
mkMakefile opts makefile
liftIO $ putStrLn ""
liftIO $ putStrLn "-----------------------------------------------------------------------------"
liftIO $ putStrLn "Generated Makefile, which uses mono. You may want to modify the paths to"
liftIO $ putStrLn "GPLEX and GPPG - unless you are sure that they are globally accessible (the"
liftIO $ putStrLn "default commands are \"mono gplex.exe\" and \"mono gppg.exe\", respectively."
liftIO $ putStrLn "The Makefile assumes that ShiftReduceParser.dll is located in ./bin and that"
liftIO $ putStrLn "is also where test.exe will be generated."
liftIO $ putStrLn "-----------------------------------------------------------------------------"
liftIO $ putStrLn ""
where
makefile basename = vcat
["MONO = mono", "MONOC = gmcs"
, "MONOCFLAGS = -optimize -reference:${PARSERREF}"
, "GPLEX = ${MONO} gplex.exe", "GPPG = ${MONO} gppg.exe"
, "PARSERREF = bin/ShiftReduceParser.dll"
, "CSFILES = Absyn.cs Parser.cs Printer.cs Scanner.cs Test.cs VisitSkeleton.cs AbstractVisitSkeleton.cs"
, Makefile.mkRule "all" [ "test" ]
[]
, Makefile.mkRule "clean" []
-- peteg: don't nuke what we generated - move that to the "vclean" target.
[ "rm -f " ++ namespace ++ ".pdf test" ]
, Makefile.mkRule "distclean" [ "clean" ]
[ "rm -f ${CSFILES}"
, "rm -f " ++ unwords [namespace <.> ext | ext <- [ "l","y","tex" ]]
, "rm -f " ++ basename ]
, Makefile.mkRule "test" [ "Parser.cs", "Scanner.cs" ]
[ "@echo \"Compiling test...\""
, "${MONOC} ${MONOCFLAGS} -out:bin/test.exe ${CSFILES}" ]
, Makefile.mkRule "Scanner.cs" [ namespace <.> "l" ]
[ "${GPLEX} /out:$@ " ++ namespace <.> "l" ]
, Makefile.mkRule "Parser.cs" [ namespace <.> "y" ]
[ "${GPPG} /gplex " ++ namespace <.> "y > $@" ]
]
writeVisualStudioFiles :: Namespace -> MkFiles ()
writeVisualStudioFiles namespace = do
guid <- projectguid
mkfile (namespace ++ ".csproj") (csproj guid)
mkfile (namespace ++ ".sln") (sln guid)
mkfile "run-gp.bat" batchfile
liftIO $ putStrLn ""
liftIO $ putStrLn "-----------------------------------------------------------------------------"
liftIO $ putStrLn "Visual Studio solution (.sln) and project (.csproj) files were written."
liftIO $ putStrLn "The project file has a reference to GPLEX/GPPG's ShiftReduceParser. You will"
liftIO $ putStrLn "have to either copy this file to bin\\ShiftReduceParser.dll or change the"
liftIO $ putStrLn "reference so that it points to the right location (you can do this from"
liftIO $ putStrLn "within Visual Studio)."
liftIO $ putStrLn "Additionally, the project includes Parser.cs and Scanner.cs. These have not"
liftIO $ putStrLn "been generated yet. You can use the run-gp.bat file to generate them, but"
liftIO $ putStrLn "note that it requires gppg and gplex to be in your PATH."
liftIO $ putStrLn "-----------------------------------------------------------------------------"
liftIO $ putStrLn ""
where
batchfile = unlines [
"@echo off",
"gppg /gplex " ++ namespace ++ ".y > Parser.cs",
"gplex /verbose /out:Scanner.cs " ++ namespace ++ ".l"
]
sln guid = unlines [
"Microsoft Visual Studio Solution File, Format Version 9.00",
"# Visual Studio 2005",
"Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"" ++ namespace ++ "\", \"" ++ namespace ++ ".csproj\", \"" ++ guid ++ "\"",
"EndProject",
"Global",
" GlobalSection(SolutionConfigurationPlatforms) = preSolution",
" Debug|Any CPU = Debug|Any CPU",
" Release|Any CPU = Release|Any CPU",
" EndGlobalSection",
" GlobalSection(ProjectConfigurationPlatforms) = postSolution",
" " ++ guid ++ ".Debug|Any CPU.ActiveCfg = Debug|Any CPU",
" " ++ guid ++ ".Debug|Any CPU.Build.0 = Debug|Any CPU",
" " ++ guid ++ ".Release|Any CPU.ActiveCfg = Release|Any CPU",
" " ++ guid ++ ".Release|Any CPU.Build.0 = Release|Any CPU",
" EndGlobalSection",
" GlobalSection(SolutionProperties) = preSolution",
" HideSolutionNode = FALSE",
" EndGlobalSection",
"EndGlobal"
]
csproj guid = unlines [
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
"<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">",
" <PropertyGroup>",
" <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>",
" <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>",
" <ProductVersion>8.0.50727</ProductVersion>",
" <SchemaVersion>2.0</SchemaVersion>",
" <ProjectGuid>" ++ guid ++ "</ProjectGuid>",
" <OutputType>Library</OutputType>",
" <AppDesignerFolder>Properties</AppDesignerFolder>",
" <RootNamespace>" ++ namespace ++ "</RootNamespace>",
" <AssemblyName>" ++ namespace ++ "</AssemblyName>",
" <StartupObject>",
" </StartupObject>",
" </PropertyGroup>",
" <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">",
" <DebugSymbols>true</DebugSymbols>",
" <DebugType>full</DebugType>",
" <Optimize>false</Optimize>",
" <OutputPath>bin\\Debug\\</OutputPath>",
" <DefineConstants>DEBUG;TRACE</DefineConstants>",
" <ErrorReport>prompt</ErrorReport>",
" <WarningLevel>4</WarningLevel>",
" </PropertyGroup>",
" <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">",
" <DebugType>pdbonly</DebugType>",
" <Optimize>true</Optimize>",
" <OutputPath>bin\\Release\\</OutputPath>",
" <DefineConstants>TRACE</DefineConstants>",
" <ErrorReport>prompt</ErrorReport>",
" <WarningLevel>4</WarningLevel>",
" </PropertyGroup>",
" <Import Project=\"$(MSBuildBinPath)\\Microsoft.CSharp.targets\" />",
" <!-- To modify your build process, add your task inside one of the targets below and uncomment it. ",
" Other similar extension points exist, see Microsoft.Common.targets.",
" <Target Name=\"BeforeBuild\">",
" </Target>",
" <Target Name=\"AfterBuild\">",
" </Target>",
" -->",
" <ItemGroup>",
" <Compile Include=\"AbstractVisitSkeleton.cs\" />",
" <Compile Include=\"Absyn.cs\" />",
" <Compile Include=\"Parser.cs\" />",
" <Compile Include=\"Printer.cs\" />",
" <Compile Include=\"Scanner.cs\" />",
" <Compile Include=\"Test.cs\" />",
" <Compile Include=\"VisitSkeleton.cs\" />",
" </ItemGroup>",
" <ItemGroup>",
" <Reference Include=\"ShiftReduceParser, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">",
" <SpecificVersion>False</SpecificVersion>",
" <HintPath>bin\\ShiftReduceParser.dll</HintPath>",
" </Reference>",
" </ItemGroup>",
" <ItemGroup>",
" <None Include=\"" ++ namespace ++ ".cf\" />",
" <None Include=\"" ++ namespace ++ ".l\" />",
" <None Include=\"" ++ namespace ++ ".y\" />",
" <None Include=\"" ++ namespace ++ ".tex\" />",
" <None Include=\"run-gp.bat\" />",
" </ItemGroup>",
" <PropertyGroup>",
" <PreBuildEvent>",
" </PreBuildEvent>",
" </PropertyGroup>",
"</Project>"
]
csharptest :: Namespace -> CF -> String
csharptest namespace cf = unlines [
"/*** Compiler Front-End Test automatically generated by the BNF Converter ***/",
"/* */",
"/* This test will parse a file, print the abstract syntax tree, and then */",
"/* pretty-print the result. */",
"/* */",
"/****************************************************************************/",
"using System;",
"using System.IO;",
"using " ++ namespace ++ ".Absyn;",
"",
"namespace " ++ namespace,
"{",
" public class Test",
" {",
" public static void Main(string[] args)",
" {",
" if (args.Length > 0)",
" {",
" Stream stream = File.OpenRead(args[0]);",
" /* The default entry point is used. For other options see class Parser */",
" Parser parser = new Parser();",
" Scanner scanner = Scanner.CreateScanner(stream);",
" // Uncomment to enable trace information:",
" // parser.Trace shows what the parser is doing",
" // parser.Trace = true;",
" // scanner.Trace prints the tokens as they are parsed, one token per line",
" // scanner.Trace = true;",
" parser.scanner = scanner;",
" try",
" {",
" " ++ def ++ " parse_tree = parser.Parse" ++ def ++ "();",
" if (parse_tree != null)",
" {",
" Console.Out.WriteLine(\"Parse Successful!\");",
" Console.Out.WriteLine(\"\");",
" Console.Out.WriteLine(\"[Abstract Syntax]\");",
" Console.Out.WriteLine(\"{0}\", PrettyPrinter.Show(parse_tree));",
" Console.Out.WriteLine(\"\");",
" Console.Out.WriteLine(\"[Linearized Tree]\");",
" Console.Out.WriteLine(\"{0}\", PrettyPrinter.Print(parse_tree));",
" }",
" else",
" {",
" Console.Out.WriteLine(\"Parse NOT Successful!\");",
" }",
" }",
" catch(Exception e)",
" {",
" Console.Out.WriteLine(\"Parse NOT Successful:\");",
" Console.Out.WriteLine(e.Message);",
" Console.Out.WriteLine(\"\");",
" Console.Out.WriteLine(\"Stack Trace:\");",
" Console.Out.WriteLine(e.StackTrace);",
" }",
" }",
" else",
" {",
" Console.Out.WriteLine(\"You must specify a filename!\");",
" }",
" }",
" }",
"}"
]
where
def = identCat (head (allEntryPoints cf))
projectguid :: MkFiles String
projectguid = do
maybeFilePath <- findDirectory
maybe getBadGUID getGoodGUID maybeFilePath
where
getBadGUID :: MkFiles String
getBadGUID = do
liftIO $ putStrLn "-----------------------------------------------------------------------------"
liftIO $ putStrLn "Could not find Visual Studio tool uuidgen.exe to generate project GUID!"
liftIO $ putStrLn "You might want to put this tool in your PATH."
liftIO $ putStrLn "-----------------------------------------------------------------------------"
return "{00000000-0000-0000-0000-000000000000}"
getGoodGUID :: FilePath -> MkFiles String
getGoodGUID filepath = liftIO $ do
let filepath' = "\"" ++ filepath ++ "\""
(_, hOut, _, _) <- runInteractiveCommand filepath'
guid <- hGetLine hOut
return ('{' : init guid ++ "}")
findDirectory :: MkFiles (Maybe FilePath)
findDirectory = liftIO $ do
-- This works with Visual Studio 2005.
-- We will probably have to be modify this to include another environment variable name for Orcas.
-- I doubt there is any need to support VS2003? (I doubt they have patched it up to have 2.0 support?)
toolpath <- catchIOError (getEnv "VS80COMNTOOLS") (\_ -> return "C:\\Program Files\\Microsoft Visual Studio 8\\Common7\\Tools")
exists <- doesDirectoryExist toolpath
if exists
then return (Just (toolpath ++ "\\uuidgen.exe"))
-- this handles the case when the user was clever enough to add the directory to his/her PATH
else findExecutable "uuidgen.exe"