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 ""
+-- "