diff --git a/source/.gitignore b/source/.gitignore index a71101303..6279c7c1f 100644 --- a/source/.gitignore +++ b/source/.gitignore @@ -1 +1 @@ -.shelly/ +.shelly/ \ No newline at end of file diff --git a/source/BNFC.cabal b/source/BNFC.cabal index f71b22312..72d0797ed 100644 --- a/source/BNFC.cabal +++ b/source/BNFC.cabal @@ -247,6 +247,17 @@ library BNFC.Backend.CPP.STL.STLUtils BNFC.Backend.CPP.STL.CFtoCVisitSkelSTL + -- F# backend + BNFC.Backend.FSharp, + BNFC.Backend.FSharp.FSharpUtil, + BNFC.Backend.FSharp.CFtoFSharpTest, + BNFC.Backend.FSharp.CFtoFSharpShow, + BNFC.Backend.FSharp.CFtoFSharpPrinter, + BNFC.Backend.FSharp.CFtoFSharpTemplate, + BNFC.Backend.FSharp.CFtoFSharpAbs, + BNFC.Backend.FSharp.CFtoFsYacc, + BNFC.Backend.FSharp.CFtoFsLex, + -- Java backend BNFC.Backend.Java BNFC.Backend.Java.CFtoAntlr4Lexer diff --git a/source/main/Main.hs b/source/main/Main.hs index 5486d60b3..ee326c60a 100644 --- a/source/main/Main.hs +++ b/source/main/Main.hs @@ -24,6 +24,7 @@ import BNFC.Backend.HaskellGADT import BNFC.Backend.Java import BNFC.Backend.Latex import BNFC.Backend.OCaml +import BNFC.Backend.FSharp import BNFC.Backend.Pygments import BNFC.CF (CF) import BNFC.GetCF @@ -79,5 +80,6 @@ maketarget = \case TargetLatex -> makeLatex TargetJava -> makeJava TargetOCaml -> makeOCaml + TargetFSharp -> makeFSharp TargetPygments -> makePygments TargetCheck -> error "impossible" diff --git a/source/src/BNFC/Backend/FSharp.hs b/source/src/BNFC/Backend/FSharp.hs new file mode 100644 index 000000000..7d9054cf0 --- /dev/null +++ b/source/src/BNFC/Backend/FSharp.hs @@ -0,0 +1,246 @@ +{- + BNF Converter: FSharp main file + Copyright (C) 2021 Author: Grzegorz Dziadkiewicz + +-} + +-- based on BNFC OCaml backend + +{-# LANGUAGE QuasiQuotes #-} + +module BNFC.Backend.FSharp (makeFSharp) where + +import System.FilePath (pathSeparator, ()) + +import BNFC.Backend.Base (MkFiles, mkfile) +import BNFC.Backend.Common.Makefile +import BNFC.Backend.FSharp.CFtoFSharpAbs +import BNFC.Backend.FSharp.CFtoFsLex +import BNFC.Backend.FSharp.CFtoFSharpPrinter +import BNFC.Backend.FSharp.CFtoFSharpShow +import BNFC.Backend.FSharp.CFtoFSharpTemplate +import BNFC.Backend.FSharp.CFtoFSharpTest (fsharpTestfile) +import BNFC.Backend.FSharp.CFtoFsYacc +import qualified BNFC.Backend.XML as XML +import BNFC.CF +import BNFC.PrettyPrint +import BNFC.Options +import BNFC.Utils +import qualified BNFC.Backend.Common.Makefile as Makefile + +import qualified BNFC.Backend.C as C + +-- naming conventions + +noLang :: SharedOptions -> String -> String +noLang _ name = name + +withLang :: SharedOptions -> String -> String +withLang opts name = name ++ sanitizedLang opts + +mkMod :: (SharedOptions -> String -> String) -> String -> SharedOptions -> String +mkMod addLang name opts = + pref ++ if inDir opts then sanitizedLang opts ++ "." ++ name else addLang opts name + where pref = maybe "" (++".") (inPackage opts) + +mkFile :: (SharedOptions -> String -> String) -> String -> String -> SharedOptions -> FilePath +mkFile addLang name ext opts = + pref ++ if inDir opts + then sanitizedLang opts name ++ ext' + else addLang opts name ++ if null ext then "" else ext' + where pref = maybe "" (\ p -> pkgToDir p "") (inPackage opts) + ext' = if null ext then "" else "." ++ ext + +-- | Turn language name into a valid fsharp module identifier. +sanitizedLang :: SharedOptions -> String +sanitizedLang = camelCase_ . lang + + +absFile, absFileM, fslexFile, fslexFileM, fsyaccFile, fsyaccFileM, + utilFile, utilFileM, templateFile, templateFileM, printerFile, printerFileM, + tFile, tFileM, showFile, showFileM, fsprojFile, buildTarget :: SharedOptions -> String +absFile = mkFile withLang "Abs" "fs" +absFileM = mkMod withLang "Abs" +fslexFile = mkFile withLang "Lex" "fsl" +fslexFileM = mkMod withLang "Lex" +fsyaccFile = mkFile withLang "Par" "fsy" +fsyaccFileM = mkMod withLang "Par" +templateFile = mkFile withLang "Skel" "fs" +templateFileM = mkMod withLang "Skel" +printerFile = mkFile withLang "Print" "fs" +printerFileM = mkMod withLang "Print" +showFile = mkFile withLang "Show" "fs" +showFileM = mkMod withLang "Show" +tFileM = mkMod withLang "Test" +tFile = mkFile withLang "Test" "fs" +utilFileM = mkMod noLang "BnfcUtil" +utilFile = mkFile noLang "BnfcUtil" "fs" +fsprojFile = mkFile withLang "" "fsproj" +buildTarget = mkFile withLang "" "" + +makeFSharp :: SharedOptions -> CF -> MkFiles () +makeFSharp opts cf = do + let absMod = absFileM opts + lexMod = fslexFileM opts + parMod = fsyaccFileM opts + prMod = printerFileM opts + showMod = showFileM opts + tFileMod = tFileM opts + do + mkfile (absFile opts) comment $ cf2Abstract absMod cf + mkfile (fslexFile opts) comment $ cf2fslex lexMod parMod cf + mkfile (fsyaccFile opts) C.comment $ + cf2fsyacc parMod absMod lexMod cf + mkfile (templateFile opts) comment $ cf2Template (templateFileM opts) absMod cf + mkfile (printerFile opts) comment $ cf2Printer prMod absMod cf + mkfile (showFile opts) comment $ cf2show showMod absMod cf + mkfile (tFile opts) comment $ fsharpTestfile absMod lexMod parMod prMod showMod tFileMod cf + mkfile (utilFile opts) comment $ utilM (utilFileM opts) + mkfile (fsprojFile opts) XML.comment $ fsprojM opts + mkMakefile opts $ makefile opts + -- case xml opts of + -- 2 -> makeXML opts True cf + -- 1 -> makeXML opts False cf + -- _ -> return () + +-- | Generate the makefile. +makefile + :: SharedOptions + -> String -- ^ Filename of the makefile. + -> Doc -- ^ Content of the makefile. +makefile opts makeFile = vcat + [ "# Makefile for building the parser and test program." + , phonyRule + , defaultRule + , vcat [ "# Rules for building the parser." , "" ] + -- If option -o was given, we have no access to the grammar file + -- from the Makefile. Thus, we have to drop the rule for + -- reinvokation of bnfc. + , when (isDefault outDir opts) $ bnfcRule + , testParserRule + , vcat [ "# Rules for cleaning generated files." , "" ] + , cleanRule + , distCleanRule + , "# EOF" + ] + where + -- | List non-file targets here. + phonyRule :: Doc + phonyRule = vcat + [ "# List of goals not corresponding to file names." + , "" + , Makefile.mkRule ".PHONY" [ "all", "clean", "distclean" ] [] + ] + -- | Default: build test parser(s). + defaultRule :: Doc + defaultRule = vcat + [ "# Default goal." + , "" + , Makefile.mkRule "all" tgts [] + ] + where + tgts = [ buildTarget opts ] + + -- | Rule to build F# test parser. + testParserRule :: Doc + testParserRule = Makefile.mkRule tgt deps [ "dotnet build" ] + where + tgt :: String + tgt = buildTarget opts + deps :: [String] + deps = map ($ opts) + [ absFile + , printerFile + , tFile + , fslexFile + , fsyaccFile + , templateFile + , showFile + , utilFile + , fsprojFile + ] + cleanRule = + mkRule "clean" [] + [ "-rm -fr bin obj "] + + distCleanRule = + mkRule "distclean" ["clean"] + [ "-rm -f " ++ unwords [ mkFile withLang "Lex" "*" opts, + mkFile withLang "Par" "*" opts, + mkFile withLang "Layout" "*" opts, + mkFile withLang "Skel" "*" opts, + mkFile withLang "Print" "*" opts, + mkFile withLang "Show" "*" opts, + mkFile withLang "Test" "*" opts, + mkFile withLang "Abs" "*" opts, + mkFile withLang "Test" "" opts, + mkFile withLang "" "fsproj" opts, + utilFile opts, + makeFile ]] + + -- | Rule to reinvoke @bnfc@ to updated parser. + -- Reinvokation should not recreate @Makefile@! + bnfcRule :: Doc + bnfcRule = Makefile.mkRule tgts [ lbnfFile opts ] [ recipe ] + where + recipe = unwords [ "bnfc", printOptions opts{ make = Nothing } ] + tgts = unwords . map ($ opts) $ + [ absFile + , fslexFile + , fsyaccFile + , utilFile + , templateFile + , printerFile + , tFile + , showFile + ] + +comment :: String -> String +comment x = unwords [ "(*", x, "*)" ] + +pkgToDir :: String -> FilePath +pkgToDir = replace '.' pathSeparator + +utilM :: String -> String +utilM moduleName = unlines + ["//automatically generated by BNFC", + "module" +++ moduleName, + "open FSharp.Text.Lexing", + "", + "exception ParseError of Position * Position " + ] + +fsprojM :: SharedOptions -> String +fsprojM opts = unlines + ["" + ,"" + ," " + ," Exe" + ," net5.0" + ," 3390;$(WarnOn)" + ," " + ,"" + ," " + ," " + ," " + ," " + ," --module " ++ fsyaccFileM opts ++ "" + ," " + ," " + ," --unicode" + ," " + ," " + ," " + ," " + ," " + ," " + ," " + ," " + ," " + ,"" + ," " + ," " + ," " + + ,"" + ] diff --git a/source/src/BNFC/Backend/FSharp/CFtoFSharpAbs.hs b/source/src/BNFC/Backend/FSharp/CFtoFSharpAbs.hs new file mode 100644 index 000000000..deaf787be --- /dev/null +++ b/source/src/BNFC/Backend/FSharp/CFtoFSharpAbs.hs @@ -0,0 +1,86 @@ +{- + BNF Converter: FSharp Abstract Syntax Generator + Copyright (C) 2021 Author: Grzegorz Dziadkiewicz + +-} + +-- based on BNFC OCaml backend + +module BNFC.Backend.FSharp.CFtoFSharpAbs (cf2Abstract) where + +import Text.PrettyPrint + +import BNFC.CF +import BNFC.Utils ( (+++), unless, parensIf ) +import Data.List ( intersperse ) +import BNFC.Backend.FSharp.FSharpUtil + +-- to produce an F# module +cf2Abstract :: String -> CF -> String +cf2Abstract absMod cf = unlines $ concat + [ ["module" +++ absMod] + , mutualRecDefs $ concat + [ map (prSpecialData cf) (specialCats cf) + , map prData (cf2data cf) + ] + , unless (null defs) $ concat + [ [ "(* defined constructors *)" + , "" + ] + , defs + ] + ] + where + defs = definedRules cf + +definedRules :: CF -> [String] +definedRules cf = map mkDef $ definitions cf + where + mkDef (Define f args e _) = + "let " ++ sanitizeFSharp (funName f) ++ " " ++ mkTuple (map fst args) ++ " = " ++ fsharpExp False e + + fsharpExp :: Bool -> Exp -> String + fsharpExp p = \case + Var s -> s + App "(:)" _ [e1, e2] -> parensIf p $ unwords [ fsharpExp True e1, "::", fsharpExp False e2 ] + App s _ [] -> sanitizeFSharp s + App s _ [e] -> parensIf p $ sanitizeFSharp s ++ ' ' : fsharpExp True e + App s _ es -> parensIf p $ sanitizeFSharp s ++ ' ' : mkTuple (map (fsharpExp False) es) + LitInt i -> show i + LitDouble d -> show d + LitChar c -> "\'" ++ c : "\'" + LitString s -> "\"" ++ s ++ "\"" + +-- allow mutual recursion so that we do not have to sort the type definitions in +-- dependency order +mutualRecDefs :: [String] -> [String] +mutualRecDefs [] = [] +mutualRecDefs (x:xs) = ("type" +++ x) : map ("and" +++) xs + +prData :: Data -> String +prData (cat,rules) = + fixType cat +++ "=" ++ + concatMap (("\n | " ++) . prRule) rules ++ + "\n" + +prRule (fun, []) = fun +prRule (fun,cats) = fun +++ "of" +++ render (mkTupleType cats) + +-- | Creates an FSharp type tuple by intercalating * between type names +-- >>> mkTupleType [Cat "A"] +-- A +-- +-- >>> mkTupleType [Cat "A", Cat "Abc", Cat "S"] +-- A * Abc * S +mkTupleType :: [Cat] -> Doc +mkTupleType = hsep . intersperse (char '*') . map (text . fixType) + +prSpecialData :: CF -> TokenCat -> String +prSpecialData cf cat = fixType (TokenCat cat) +++ "=" +++ fixType (TokenCat cat) +++ "of" +++ contentSpec cf cat + +-- unwords ["newtype",cat,"=",cat,contentSpec cf cat,"deriving (Eq,Ord,Show)"] + +contentSpec :: CF -> TokenCat -> String +contentSpec cf cat + | isPositionCat cf cat = "((int * int) * string)" + | otherwise = "string" diff --git a/source/src/BNFC/Backend/FSharp/CFtoFSharpPrinter.hs b/source/src/BNFC/Backend/FSharp/CFtoFSharpPrinter.hs new file mode 100644 index 000000000..33b200217 --- /dev/null +++ b/source/src/BNFC/Backend/FSharp/CFtoFSharpPrinter.hs @@ -0,0 +1,229 @@ +{- + BNF Converter: Pretty-printer generator + Copyright (C) 2021 Author: Grzegorz Dziadkiewicz + +-} + +-- based on BNFC OCaml backend + + +{-# LANGUAGE OverloadedStrings #-} + +module BNFC.Backend.FSharp.CFtoFSharpPrinter (cf2Printer, prtFun) where + +import Prelude hiding ((<>)) + +import Data.Char(toLower) +import Data.List (intersperse, sortBy) +import Data.Maybe (fromJust) + +import BNFC.CF +import BNFC.Utils +import BNFC.Backend.FSharp.FSharpUtil +import BNFC.PrettyPrint +import BNFC.Backend.Haskell.CFtoPrinter (compareRules) + + +-- derive pretty-printer from a BNF grammar. AR 15/2/2002 +cf2Printer :: String -> ModuleName -> CF -> String +cf2Printer name absMod cf = unlines [ + prologue name absMod, + charRule cf, + integerRule cf, + doubleRule cf, + stringRule cf, + if hasIdent cf then identRule absMod cf else "", + unlines [ownPrintRule absMod cf own | (own,_) <- tokenPragmas cf, own /= "Integer" && own /= "Double"], + rules absMod cf + ] + +prologue :: String -> String -> String +prologue name absMod = unlines [ + "// pretty-printer", + "module " ++ name, + "", + "open System", + "open System.Text", + "open " ++ absMod, + "open Printf", + "", + "type Doc = StringBuilder -> int -> int", + "", + "let rec printTree (printer : int -> 'a -> Doc) (tree : 'a) : string = ", + " let initSize = 16", + " let buffer = StringBuilder initSize", + " printer 0 tree buffer 0 |> ignore", + " buffer.ToString()", + "", + "let indentWidth = 4", + "", + "let indent (i: int) : string = \"\\n\" + String.replicate i \" \"", + "let append (s:string) (sb:StringBuilder) = sb.Append s", + "", + "// this render function is written for C-style languages, you may want to change it", + "let render (s : string) : Doc = fun buf i -> ", + " let n = buf.Length", + " let last = if n = 0 then None else Some (buf.Chars (n-1))", + " let newindent =", + " match s with", + " | \"{\" -> i + indentWidth", + " | \"}\" -> i - indentWidth", + " | _ -> i", + " let whitespace =", + " match last with", + " | None -> \"\" ", + " | Some '}' ->", + " match s with", + " | \";\" -> \"\"", + " | _ -> indent newindent", + " | Some '{' | Some ';' when s = \"}\" -> indent newindent", + " | Some '{' | Some ';' -> indent i", + " | Some '[' | Some '(' -> \"\"", + " | Some c when Char.IsWhiteSpace c -> \"\"", + " | Some _ ->", + " match s with", + " | \";\" | \",\" | \")\" | \"]\" -> \"\"", + " | \"{\" -> indent i", + " | \"}\" -> indent newindent", + " | _ when String.IsNullOrWhiteSpace s -> \"\"", + " | _ -> \" \"", + " buf |> append whitespace |> append s |> ignore", + " newindent", + "", + "let emptyDoc : Doc = fun _ i -> i", + "", + "let concatD (ds : Doc list) : Doc = fun buf i -> ", + " List.fold (fun accIndent elemDoc -> elemDoc buf accIndent) (emptyDoc buf i) ds", + "", + "let parenth (d:Doc) : Doc = concatD [render \"(\"; d; render \")\"]", + "", + "let prPrec (i:int) (j:int) (d:Doc) : Doc = if j CF -> TokenCat -> String +ownPrintRule absMod cf own = unlines $ [ + "let rec" +++ prtFun (TokenCat own) +++ "_ (" ++ absMod ++ "." ++ fixType (TokenCat own) ++ posn ++ ") : Doc = render i", + ifList cf (TokenCat own) + ] + where + posn = if isPositionCat cf own then " (_,i)" else " i" + +-- copy and paste from CFtoTemplate + +rules :: ModuleName -> CF -> String +rules absMod cf = unlines $ mutualDefs $ + map (\(s,xs) -> case_fun absMod s (map toArgs xs) ++ ifList cf s) $ cf2data cf + where + reserved = "i":"e":reservedFSharp + toArgs (cons,args) = ((cons, mkNames reserved LowerCase (map var args)), ruleOf cons) + var (ListCat c) = var c ++ "s" + var (Cat "Ident") = "id" + var (Cat "Integer") = "n" + var (Cat "String") = "str" + var (Cat "Char") = "c" + var (Cat "Double") = "d" + var xs = map toLower (catToStr xs) + ruleOf s = fromJust $ lookupRule (noPosition s) (cfgRules cf) + +--- case_fun :: Cat -> [(Constructor,Rule)] -> String +case_fun absMod cat xs = unlines [ +-- "instance Print" +++ cat +++ "where", + prtFun cat +++"(i:int)" +++ "(e : " ++ fixTypeQual absMod cat ++ ") : Doc =", + " match e with", + unlines $ map (indent 1) $ insertBar $ map g xs + ] + where + g ((c,xx),r) = + " " ++ absMod ++ "." ++ c +++ mkTuple xx +++ "->" +++ + "prPrec i" +++ show (precCat (fst r)) +++ mkRhs xx (snd r) + +-- ifList cf cat = mkListRule $ nil cat ++ one cat ++ cons cat where +-- nil cat = [" [] -> " ++ mkRhs [] its | +-- Rule f c its <- rulesOfCF cf, isNilFun f , normCatOfList c == cat] +-- one cat = [" | [x] -> " ++ mkRhs ["x"] its | +-- Rule f c its <- rulesOfCF cf, isOneFun f , normCatOfList c == cat] +-- cons cat = [" | x::xs -> " ++ mkRhs ["x","xs"] its | +-- Rule f c its <- rulesOfCF cf, isConsFun f , normCatOfList c == cat] +-- mkListRule [] = "" +-- mkListRule rs = unlines $ ("and prt" ++ fixTypeUpper cat ++ "ListBNFC" +++ "_ es : doc = match es with"):rs + +ifList :: CF -> Cat -> String +ifList cf cat = render $ + case cases of + [] -> empty + xs -> vcat + [ "and prt" <> text (fixTypeUpper cat) <> "ListBNFC i es : Doc =" + , nest 4 "match (i, es) with" + , nest 4 $ vcat (map ("|" <+>) xs) + ] + where + rules = sortBy compareRules $ rulesForNormalizedCat cf (ListCat cat) + cases = [ d | r <- rules, let d = mkPrtListCase r, not (isEmpty d) ] + + +-- | Pattern match on the list constructor and the coercion level +-- +-- >>> mkPrtListCase (npRule "[]" (ListCat (Cat "Foo")) [] Parsable) +-- (_,[]) -> (concatD []) +-- +-- >>> mkPrtListCase (npRule "(:[])" (ListCat (Cat "Foo")) [Left (Cat "Foo")] Parsable) +-- (_,[x]) -> (concatD [prtFoo 0 x]) +-- +-- >>> mkPrtListCase (npRule "(:)" (ListCat (Cat "Foo")) [Left (Cat "Foo"), Left (ListCat (Cat "Foo"))] Parsable) +-- (_,x::xs) -> (concatD [prtFoo 0 x ; prtFooListBNFC 0 xs]) +-- +-- >>> mkPrtListCase (npRule "[]" (ListCat (CoercCat "Foo" 2)) [] Parsable) +-- (2,[]) -> (concatD []) +-- +-- >>> mkPrtListCase (npRule "(:[])" (ListCat (CoercCat "Foo" 2)) [Left (CoercCat "Foo" 2)] Parsable) +-- (2,[x]) -> (concatD [prtFoo 2 x]) +-- +-- >>> mkPrtListCase (npRule "(:)" (ListCat (CoercCat "Foo" 2)) [Left (CoercCat "Foo" 2), Left (ListCat (CoercCat "Foo" 2))] Parsable) +-- (2,x::xs) -> (concatD [prtFoo 2 x ; prtFooListBNFC 2 xs]) +-- +mkPrtListCase :: Rule -> Doc +mkPrtListCase (Rule f (WithPosition _ (ListCat c)) rhs _) + | isNilFun f = parens (precPattern <> "," <> "[]") <+> "->" <+> body + | isOneFun f = parens (precPattern <> "," <> "[x]") <+> "->" <+> body + | isConsFun f = parens (precPattern <> "," <>"x::xs") <+> "->" <+> body + | otherwise = empty -- (++) constructor + where + precPattern = case precCat c of 0 -> "_" ; p -> integer p + body = text $ mkRhs ["x", "xs"] rhs +mkPrtListCase _ = error "mkPrtListCase undefined for non-list categories" + +mkRhs args its = + "(concatD [" ++ unwords (intersperse ";" (mk args its)) ++ "])" + where + mk (arg:args) (Left c : items) = (prt c +++ arg) : mk args items + mk args (Right s : items) = ("render " ++ mkEsc s) : mk args items + mk _ _ = [] + prt c = prtFun c +++ show (precCat c) + diff --git a/source/src/BNFC/Backend/FSharp/CFtoFSharpShow.hs b/source/src/BNFC/Backend/FSharp/CFtoFSharpShow.hs new file mode 100644 index 000000000..356ad082c --- /dev/null +++ b/source/src/BNFC/Backend/FSharp/CFtoFSharpShow.hs @@ -0,0 +1,133 @@ +{- + TODO: Check if printing fot native fsharp objects like choice types or algebraic types can handle this (it would allow to remove most of the code here) + BNF Converter: Non-pretty-printer generator + Copyright (C) 2021 Author: Grzegorz Dziadkiewicz + +-} + + +{-# LANGUAGE LambdaCase #-} + +module BNFC.Backend.FSharp.CFtoFSharpShow (cf2show, showsFunQual) where + +import Data.Char(toLower) +import Data.List (intersperse) +import Data.Maybe (fromJust) + +import BNFC.CF +import BNFC.Utils +import BNFC.Backend.FSharp.FSharpUtil + +cf2show :: String -> ModuleName -> CF -> String +cf2show name absMod cf = unlines + [ prologue name absMod + , integerRule + , doubleRule + , if hasIdent cf then identRule absMod cf else "" + , unlines [ ownPrintRule absMod cf own | (own,_) <- tokenPragmas cf, own /= "Integer" && own /= "Double"] + , rules absMod cf + ] + +prologue :: String -> String -> String +prologue name absMod = unlines [ + "// show functions", + "module " ++ name, + "", + "open System.Text", + "open " ++ absMod, + "", + "type Showable = StringBuilder -> unit", + "", + "let show (s : Showable) : string = ", + " let initSize = 16", + " let b = StringBuilder initSize", + " s b", + " b.ToString()", + "", + "let emptyS : Showable = ignore", + "", + "let c2s (c:char) : Showable = fun buf -> buf.Append c |> ignore", + "let s2s (s:string) : Showable = fun buf -> buf.Append s |> ignore", + "", + "let ( >> ) (s1:Showable) (s2:Showable) : Showable = fun buf ->", + " s1 buf", + " s2 buf", + "", + "let " ++ showsFun (TokenCat "Char") ++ " (c:char) : Showable = fun buf -> ", + " buf.Append (\"'\" + string c + \"'\") |> ignore", + "", + "let " ++ showsFun (TokenCat "String") ++ " (s:string) : Showable = fun buf -> ", + " buf.Append (\"\\\"\" + s + \"\\\"\") |> ignore", + "", + "let showList (showFun : 'a -> Showable) (xs : 'a list) : Showable = fun buf -> ", + " let rec f ys =", + " match ys with", + " | [] -> ()", + " | [y] -> showFun y buf", + " | y::ys ->", + " showFun y buf", + " buf.Append \"; \" |> ignore", + " f ys ", + " buf.Append '[' |> ignore", + " f xs;", + " buf.Append ']' |> ignore" + ] + +integerRule = "let showint (i:int) : Showable = i |> string |> s2s" + +doubleRule = "let showfloat (f:float) : Showable = f |> string |> s2s" + +identRule absMod cf = ownPrintRule absMod cf catIdent + +ownPrintRule :: ModuleName -> CF -> TokenCat -> String +ownPrintRule absMod cf own = + "let rec" +++ showsFun tokenCat +++ "(" ++ fixTypeQual absMod tokenCat ++ posn ++ ") : Showable = s2s \"" + ++ own ++ " \" >> showstring i" + where + tokenCat = TokenCat own + posn = if isPositionCat cf own then " (_,i)" else " i" + +-- copy and paste from CFtoTemplate + +rules :: ModuleName -> CF -> String +rules absMod cf = unlines $ mutualDefs $ + map (\ (s, xs) -> case_fun absMod s $ map toArgs xs) $ cf2data cf + where + toArgs (cons,args) = ((cons, names (map (sanitizeFSharp . var) args) (0 :: Int)), + ruleOf cons) + names [] _ = [] + names (x:xs) n + | x `elem` xs = (x ++ show n) : names xs (n+1) + | otherwise = x : names xs n + var (ListCat c) = var c ++ "s" + var (Cat "Ident") = "id" + var (Cat "Integer") = "n" + var (Cat "String") = "str" + var (Cat "Char") = "c" + var (Cat "Double") = "d" + var cat = map toLower (catToStr cat) + ruleOf s = fromJust $ lookupRule (noPosition s) (cfgRules cf) + +-- case_fun :: Cat -> [(Constructor,Rule)] -> String +case_fun absMod cat xs = unlines [ + showsFun cat +++ "(e : " ++ fixType cat ++ ") : Showable =", + indent 4 "match e with", + unlines $ map (indent 4) $ insertBar $ map f xs + ] + where + f ((c,xx),r) = + " " ++ absMod ++ "." ++ c +++ mkTuple xx +++ "->" +++ + "s2s" +++ show c +++ + case mkRhs xx (snd r) of + [] -> [] + str -> ">> c2s ' ' >> " ++ str + +mkRhs args its = + case unwords (intersperse " >> s2s \", \" >> " (mk args its)) of + [] -> "" + str -> "c2s '(' >> " ++ str ++ " >> c2s ')'" + where + mk (arg:args) (Left c : items) = (showsFun c +++ arg) : mk args items + mk args (Right _ : items) = mk args items + mk _ _ = [] + diff --git a/source/src/BNFC/Backend/FSharp/CFtoFSharpTemplate.hs b/source/src/BNFC/Backend/FSharp/CFtoFSharpTemplate.hs new file mode 100644 index 000000000..5b9005ba4 --- /dev/null +++ b/source/src/BNFC/Backend/FSharp/CFtoFSharpTemplate.hs @@ -0,0 +1,66 @@ +{- + BNF Converter: Template Generator + Copyright (C) 2021 Author: Grzegorz Dziadkiewicz + +-} + +-- based on BNFC OCaml backend + + +module BNFC.Backend.FSharp.CFtoFSharpTemplate ( + cf2Template + ) where + +import BNFC.CF +import BNFC.Utils((+++)) +import Data.Char +import BNFC.Backend.FSharp.FSharpUtil + + +type ModuleName = String +type Constructor = String + +cf2Template :: ModuleName -> ModuleName -> CF -> String +cf2Template skelName absName cf = unlines + [ + "// FSharp module generated by the BNF converter", + "module "++ skelName, + "", + "open " ++ absName, + "", + "type Result = string", + "", + "let failure x = failwith" +++ "\"Undefined case.\"", + "", + unlines $ mutualDefs $ map (\(s,xs) -> case_fun s (toArgs xs)) $ specialData cf ++ cf2data cf + ] + +--TODO: Rewrite in a more readeable way +toArgs [] = [] +toArgs ((cons,args):xs) + = (fixType (TokenCat cons) ++ " " ++ mkTuple (names (map (sanitizeFSharp . var) args) (0 :: Int))) : toArgs xs + +--TODO: It is probably bugy for input like ["a", "a", "b", "b"] +names :: [String] -> Int -> [String] +names [] _ = [] +names (x:xs) n + | x `elem` xs = (x ++ show n) : names xs (n+1) + | otherwise = x : names xs n + +var:: Cat -> String +var (ListCat c) = var c ++ "s" +var (Cat "Ident") = "id" +var (Cat "Integer") = "n" +var (Cat "String") = "str" +var (Cat "Char") = "c" +var (Cat "Double") = "d" +var cat = map toLower (show cat) + +case_fun :: Cat -> [Constructor] -> String +case_fun cat xs = unlines $ + [ + "trans" ++ show cat +++ "(x : " ++ fixType cat ++ ") : Result =", + indent 1 "match x with" + ] ++ + (map (indent 1) . insertBar . map (++ " -> failure x")) xs + diff --git a/source/src/BNFC/Backend/FSharp/CFtoFSharpTest.hs b/source/src/BNFC/Backend/FSharp/CFtoFSharpTest.hs new file mode 100644 index 000000000..e9f885db8 --- /dev/null +++ b/source/src/BNFC/Backend/FSharp/CFtoFSharpTest.hs @@ -0,0 +1,83 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} + +{- + BNF Converter: Generate main/test module for F# + Copyright (C) 2021 Author: Grzegorz DZiadkiewicz + +-} + +module BNFC.Backend.FSharp.CFtoFSharpTest where + +import Prelude hiding ((<>)) + +import Text.PrettyPrint + +import BNFC.CF +import BNFC.Utils((+++)) +import BNFC.Backend.FSharp.FSharpUtil + +-- | F# comment +-- >>> comment "I'm a comment" +-- // I'm a comment +comment :: Doc -> Doc +comment d = "//" <+> d + +-- | F# String concatenation +-- >>> "print a" <^> doubleQuotes "abc" +-- print a + "abc" +a <^> b = a <+> "+" <+> b + +-- | Generate a test program in F# +fsharpTestfile :: String -> String -> String -> String -> String -> String -> CF -> Doc +fsharpTestfile absM lexM parM printM showM moduleName cf = + let + lexerName = "token" + parserName = "p" <> topType + printerName = "printTree " <> text (prtFun (firstEntry cf)) + showFun = parens ("fun t -> " <+> text (showsFun (firstEntry cf)) <+> "t" <+> "|>" <+> "show") + topType = text $ nonterminal $ firstEntry cf + topType2 = text $ fixType $ normCat $ firstEntry cf + in vcat + [ comment "automatically generated by the BNF Converter" + , text ("module" +++ moduleName) + , "open System" + , "open System.IO" + , "open FSharp.Text.Lexing" + , text ("open" +++ absM) + , text ("open" +++ printM) + , text ("open" +++ showM) + , text ("open" +++ parM) + , text ("open" +++ lexM) + , "" + , "let parse (c:TextReader) :" <+> topType2 <+> "=" + , nest 4 ("c |> LexBuffer.FromTextReader |>" <+> parserName <+> lexerName ) + , "" + , "let showTree (t:" <> topType2 <> ") : string =" + , nest 4 (vcat ( punctuate " +" + [ doubleQuotes "[Abstract syntax]\\n\\n" + , showFun <+> "t" + , doubleQuotes "\\n\\n" + , doubleQuotes "[Linearized tree]\\n\\n" + , printerName <+> "t" + , doubleQuotes "\\n" ] ) ) + , "" + , "[]" + , "let main args =" + , nest 4 $ vcat + [ "use channel =" + , nest 4 $ vcat + [ "if Array.length args > 0 then File.OpenText args.[0] :> TextReader" + , "else stdin" ] + , "try" + , nest 4 $ vcat + [ "channel |> parse |> showTree |> printfn \"%s\"" + , "0"] + , "with BnfcUtil.ParseError (start_pos, end_pos) ->" + , nest 4 $ vcat + [ "printfn" <+> doubleQuotes "Parse error at %d.%d-%d.%d" + , nest 4 $ vcat + [ "start_pos.pos_lnum (start_pos.pos_cnum - start_pos.pos_bol)" + , "end_pos.pos_lnum (end_pos.pos_cnum - end_pos.pos_bol)" ] + , "1" ]] + ] diff --git a/source/src/BNFC/Backend/FSharp/CFtoFsLex.hs b/source/src/BNFC/Backend/FSharp/CFtoFsLex.hs new file mode 100644 index 000000000..db87bb981 --- /dev/null +++ b/source/src/BNFC/Backend/FSharp/CFtoFsLex.hs @@ -0,0 +1,292 @@ +{- + BNF Converter: fslex Generator + Copyright (C) 2021 Author: Grzegorz Dziadkiewicz + +-} + + +-- based on BNFC OCaml backend + +module BNFC.Backend.FSharp.CFtoFsLex (cf2fslex) where + +import Prelude hiding ((<>)) + +import Data.Char (ord) +import qualified Data.List as List +import Text.PrettyPrint hiding (render) +import qualified Text.PrettyPrint as PP + +import BNFC.Abs +import BNFC.CF +import BNFC.Backend.Common (asciiKeywords, unicodeAndSymbols) +import BNFC.Backend.FSharp.CFtoFsYacc (terminal) +import BNFC.Backend.FSharp.FSharpUtil (mkEsc, fsharpTokenName, indent) +import BNFC.Lexing (mkRegMultilineComment) +import BNFC.Utils (cstring, unless, (+++)) + +cf2fslex :: String -> String -> CF -> String +cf2fslex lexMod parserMod cf = unlines $ List.intercalate [""] + [ header lexMod parserMod cf + , cMacros + , rMacros cf + , uMacros cf + , [ PP.render $ rules cf ] + ] + +header :: String -> String -> CF -> [String] +header lexerMod parserMod cf = List.intercalate [""] . filter (not . null) $ concat + [ [ [ "(* Lexer definition for fslex. *)" + , "{" + , "module " ++ lexerMod + , "open " ++ parserMod + , "open System" + , "open System.Collections.Generic" + , "open FSharp.Text.Lexing" + , "" + ] + ] + , hashtables cf + , [ [ "let unescapeInitTail (s:string) : string =" + , " let rec unesc s =" + , " match s with" + , " | '\\\\'::c::cs when List.contains c ['\\\"'; '\\\\'; '\\\''] -> c :: unesc cs" + , " | '\\\\'::'n'::cs -> '\\n' :: unesc cs" + , " | '\\\\'::'t'::cs -> '\\t' :: unesc cs" + , " | '\\\"'::[] -> []" + , " | c::cs -> c :: unesc cs" + , " | _ -> []" + , " s.ToCharArray() |> List.ofArray |> List.tail |> unesc |> Array.ofList |> String" + , "" + , "let incr_lineno (lexbuf:LexBuffer<_>) : unit =" + , " lexbuf.EndPos <- lexbuf.EndPos.NextLine" + , "let lexeme (lexbuf:LexBuffer<_>) : string =" + , " LexBuffer<_>.LexemeString lexbuf" + , "}" + ] + ] + ] + +-- | Set up hashtables for reserved symbols and words. +hashtables :: CF -> [[String]] +hashtables cf = + [ ht "symbol_table" $ unicodeAndSymbols cf + , ht "resword_table" $ asciiKeywords cf + ] + where + ht :: String -> [String] -> [String] + ht table syms = unless (null syms) $ + [ "let" +++ table +++ "=" + , indent 1 "Map.ofList" + , indent 2 "[" + , unlines $ map (indent 3) keyvals + , indent 2 "]" + ] + where + keyvals = map (\ s -> concat [ "(", mkEsc s, ", ", terminal cf s, ")" ]) syms + +cMacros :: [String] +cMacros = + [ "(* BNFC character classes *)" + , "let letter = ['a'-'z' 'A'-'Z' '\\192'-'\\214' '\\216'-'\\246' '\\248'-'\\255'] (* isolatin1 letter FIXME *)" + , "let upper = ['A'-'Z' '\\192'-'\\214' '\\216'-'\\221'] (* capital isolatin1 letter FIXME *)" + , "let lower = ['a'-'z' '\\222'-'\\246' '\\248'-'\\255'] (* small isolatin1 letter FIXME *)" + , "let digit = ['0'-'9'] (* digit *)" + , "let idchar = letter | digit | ['_' '\\''] (* identifier character *)" + , "let universal = _ (* universal: any character *)" + ] + +rMacros :: CF -> [String] +rMacros cf + | null symbs = [] + | otherwise = + [ "(* reserved words consisting of special symbols *)" + , unwords $ "let rsyms =" : List.intersperse "|" (map mkEsc symbs) + ] + where symbs = unicodeAndSymbols cf + +-- user macros, derived from the user-defined tokens +uMacros :: CF -> [String] +uMacros cf = if null res then [] else "(* user-defined token types *)" : res + where res = ["let " ++ name ++ " = " ++ rep | (name, rep, _, _) <- userTokens cf] + +-- | Returns the tuple of @(reg_name, reg_representation, token_name, is_position_token)@. + +userTokens :: CF -> [(String, String, String, Bool)] +userTokens cf = + [ (fsharpTokenName name, printRegFSharp reg, name, pos) + | TokenReg n pos reg <- cfgPragmas cf + , let name = wpThing n + ] + +-- | Make FsLex rule +-- >>> mkRule "token" [("REGEX1","ACTION1"),("REGULAREXPRESSION2","ACTION2"),("...","...")] +-- (* lexing rules *) +-- rule token = +-- parse REGEX1 { ACTION1 } +-- | REGULAREXPRESSION2 +-- { ACTION2 } +-- | ... { ... } +-- +-- If no regex are given, we dont create a lexer rule: +-- >>> mkRule "empty" [] +-- +mkRule :: Doc -> [(Doc,Doc)] -> Doc +mkRule _ [] = empty +mkRule entrypoint (r:rs) = vcat + [ "(* lexing rules *)" + , "rule" <+> entrypoint <+> "=" + , nest 2 $ hang "parse" 4 $ vcat $ + nest 2 (mkOne r) : map (("|" <+>) . mkOne) rs + ] + where + mkOne (regex, action) = regex $$ nest 8 (hsep ["{", action, "}"]) + +-- | Create regex for single line comments +-- >>> mkRegexSingleLineComment "--" +-- "--" [^'\n']* +-- >>> mkRegexSingleLineComment "\"" +-- "\"" [^'\n']* +mkRegexSingleLineComment :: String -> Doc +mkRegexSingleLineComment s = cstring s <+> "[^'\\n']*" + +-- | Create regex for multiline comments. +-- >>> mkRegexMultilineComment "" +-- "