diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..c7efda6e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,50 @@ + +{ + // Automatically created by phoityne-vscode extension. + + "version": "2.0.0", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "tasks": [ + { + // F7 + "group": { + "kind": "build", + "isDefault": true + }, + "label": "haskell build", + "type": "shell", + //"command": "cabal configure && cabal build" + "command": "stack build" + }, + { + // F6 + "group": "build", + "type": "shell", + "label": "haskell clean & build", + //"command": "cabal clean && cabal configure && cabal build" + "command": "stack clean && stack build" + //"command": "stack clean ; stack build" // for powershell + }, + { + // F8 + "group": { + "kind": "test", + "isDefault": true + }, + "type": "shell", + "label": "haskell test", + //"command": "cabal test" + "command": "stack test" + }, + { + // F6 + "isBackground": true, + "type": "shell", + "label": "haskell watch", + "command": "stack build --test --no-run-tests --file-watch" + } + ] +} diff --git a/source/BNFC.cabal b/source/BNFC.cabal index 7fa96005..00a6b9c7 100644 --- a/source/BNFC.cabal +++ b/source/BNFC.cabal @@ -264,6 +264,29 @@ library BNFC.Backend.Java.RegToAntlrLexer BNFC.Backend.Java.Utils + -- Dart backend + BNFC.Backend.Dart + BNFC.Backend.Dart.CFtoDartAST + BNFC.Backend.Dart.CFtoDartBuilder + BNFC.Backend.Dart.Common + BNFC.Backend.Dart.CFtoDartPrinter + BNFC.Backend.Dart.CFtoDartSkeleton + + -- Swift backend + BNFC.Backend.Swift + BNFC.Backend.Swift.CFtoSwiftAST + BNFC.Backend.Swift.CFtoSwiftBuilder + BNFC.Backend.Swift.Common + BNFC.Backend.Swift.CFtoSwiftPrinter + BNFC.Backend.Swift.CFtoSwiftSkeleton + + -- Antlr4 backend + BNFC.Backend.Antlr + BNFC.Backend.Antlr.CFtoAntlr4Lexer + BNFC.Backend.Antlr.CFtoAntlr4Parser + BNFC.Backend.Antlr.RegToAntlrLexer + BNFC.Backend.Antlr.Utils + -- XML backend BNFC.Backend.XML diff --git a/source/main/Main.hs b/source/main/Main.hs index 754bf268..554841d9 100644 --- a/source/main/Main.hs +++ b/source/main/Main.hs @@ -25,6 +25,9 @@ import BNFC.Backend.Java import BNFC.Backend.Latex import BNFC.Backend.OCaml import BNFC.Backend.Pygments +import BNFC.Backend.Dart (makeDart) +import BNFC.Backend.Swift (makeSwift) +import BNFC.Backend.Antlr import BNFC.Backend.TreeSitter import BNFC.CF (CF) import BNFC.GetCF @@ -81,5 +84,8 @@ maketarget = \case TargetJava -> makeJava TargetOCaml -> makeOCaml TargetPygments -> makePygments + TargetDart -> makeDart + TargetSwift -> makeSwift + TargetAntlr -> makeAntlr TargetCheck -> error "impossible" TargetTreeSitter -> makeTreeSitter diff --git a/source/src/BNFC/Backend/Antlr.hs b/source/src/BNFC/Backend/Antlr.hs new file mode 100644 index 00000000..b83f661a --- /dev/null +++ b/source/src/BNFC/Backend/Antlr.hs @@ -0,0 +1,101 @@ +{-# LANGUAGE RecordWildCards #-} + +module BNFC.Backend.Antlr ( makeAntlr, makeAntlr', DirectoryOptions(..) ) where + +import Prelude hiding ((<>)) +import System.FilePath ((), pathSeparator, (<.>)) +import Text.PrettyPrint.HughesPJ (vcat) +import Data.Maybe (fromMaybe) + +import BNFC.Utils + ( NameStyle(CamelCase), + mkName, + replace, + (+.+), + (+++) ) +import BNFC.CF +import BNFC.Options as Options +import BNFC.Backend.Base +import BNFC.Backend.Antlr.CFtoAntlr4Lexer +import BNFC.Backend.Antlr.CFtoAntlr4Parser +import BNFC.Backend.Antlr.Utils (dotG4, getAntlrOptions) +import BNFC.Backend.Common.Makefile as MakeFile + ( mkMakefile, mkVar, mkRule, refVar ) + +data DirectoryOptions = DirectoryOptions + { baseDirectory :: Maybe String + , nameStyle :: Maybe NameStyle } + +makeAntlr :: SharedOptions -> CF -> MkFiles () +makeAntlr opts cf = makeAntlr' opts cf DirectoryOptions { + baseDirectory=Nothing + , nameStyle=Nothing } + +makeAntlr' :: SharedOptions -> CF -> DirectoryOptions -> MkFiles () +makeAntlr' opts@Options{..} cf DirectoryOptions{..} = do + let packageBase = maybe id (+.+) inPackage pkg + dirBase = fromMaybe (pkgToDir packageBase) baseDirectory + + let lexerName = mkFilename "Lexer" + lexerFile = dotG4 lexerName + (lex, env) = cf2AntlrLex lexerName cf + -- Where the lexer file is created. lex is the content! + mkfile (dirBase lexerFile) mkAntlrComment lex + + let parserName = mkFilename "Parser" + parserFile = dotG4 parserName + parserContent = cf2AntlrParse lexerName parserName cf linenumbers env + mkfile (dirBase parserFile) mkAntlrComment parserContent + + MakeFile.mkMakefile optMake makefileContent + where + pkg = mkName [] (fromMaybe CamelCase nameStyle) lang + pkgToDir = replace '.' pathSeparator + mkFilename ending = mkName [] (fromMaybe CamelCase nameStyle) (pkg ++ ending) + + makeVars x = [MakeFile.mkVar n v | (n,v) <- x] + makeRules x = [MakeFile.mkRule tar dep recipe | (tar, dep, recipe) <- x] + + langRef = MakeFile.refVar "LANG" + + lexerVarName = "LEXER_GRAMMAR_FILENAME" + lexerGrammarFile = (langRef ) . dotG4 $ langRef ++ "Lexer" + + parserVarName = "PARSER_GRAMMAR_FILENAME" + parserGrammarFile = (langRef ) . dotG4 $ langRef ++ "Parser" + + makefileVars = vcat $ makeVars + [ ("LANG", pkg) + , (lexerVarName, lexerGrammarFile) + , (parserVarName, parserGrammarFile) + , ("ANTLR4", "java org.antlr.v4.Tool") + , ("ANTLR_OPTIONS", getAntlrOptions opts) + , ("DIRECT_OPTIONS", antlrOpts) + ] + + genAntlrRecipe = ((MakeFile.refVar "ANTLR4" +++ MakeFile.refVar "ANTLR_OPTIONS" +++ MakeFile.refVar "DIRECT_OPTIONS") +++) . MakeFile.refVar + + antlrFiles = + let ns = fromMaybe CamelCase nameStyle + in map (langRef ) + [ mkName [] ns (pkg +++ "Lexer") <.> "interp" + , mkName [] ns (pkg +++ "Parser") <.> "interp" + , mkName [] ns (pkg +++ "Lexer") <.> "tokens" + , mkName [] ns (pkg +++ "Parser") <.> "tokens" + ] + + makefileRules = vcat $ makeRules + [ (".PHONY", ["all", "clean-antlr", "remove"], []) + , ("all", [langRef], []) + , ("lexer", [MakeFile.refVar lexerVarName], [genAntlrRecipe lexerVarName]) + , ("parser", [MakeFile.refVar parserVarName], [genAntlrRecipe parserVarName]) + , (langRef, ["lexer", "parser"], []) + , ("clean-antlr", [], + map ("rm -f" +++) antlrFiles ) + , ("remove", [], ["rm -rf" +++ langRef]) + ] + + makefileContent _ = vcat [makefileVars, "", makefileRules] + +mkAntlrComment :: String -> String +mkAntlrComment = ("// ANTLRv4 " ++) diff --git a/source/src/BNFC/Backend/Antlr/CFtoAntlr4Lexer.hs b/source/src/BNFC/Backend/Antlr/CFtoAntlr4Lexer.hs new file mode 100644 index 00000000..c35d34a8 --- /dev/null +++ b/source/src/BNFC/Backend/Antlr/CFtoAntlr4Lexer.hs @@ -0,0 +1,173 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} + +{- + BNF Converter: Java Antlr4 Lexer generator + Copyright (C) 2015 Author: Gabriele Paganelli + + Description : This module generates the Antlr4 input file. + Based on CFtoJLex15.hs + + Author : Gabriele Paganelli (gapag@distruzione.org) + Created : 15 Oct, 2015 + +-} + +module BNFC.Backend.Antlr.CFtoAntlr4Lexer ( cf2AntlrLex ) where + +import Prelude hiding ((<>)) + +import Text.PrettyPrint +import BNFC.CF +import BNFC.Backend.Antlr.RegToAntlrLexer +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, KeywordEnv) +cf2AntlrLex lexerName cf = (,env) $ vcat + [ prelude lexerName + , cMacros + -- unnamed symbols (those in quotes, not in token definitions) + , lexSymbols env + , restOfLexerGrammar cf + ] + where + env = zip (cfgSymbols cf ++ reservedWords cf) $ map (("Surrogate_id_SYMB_" ++) . show) [0 :: Int ..] + + +-- | File prelude +prelude :: String -> Doc +prelude lexerName = vcat + [ "// Lexer definition for use with Antlr4" + , "lexer grammar" <+> text lexerName <> ";" + ] + +--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 escapeCharInSingleQuotes + +-- | +-- >>> lexSymbols [("foo","bar")] +-- bar : 'foo' ; +-- >>> lexSymbols [("\\","bar")] +-- bar : '\\' ; +-- >>> lexSymbols [("/","bar")] +-- bar : '/' ; +-- >>> lexSymbols [("~","bar")] +-- bar : '~' ; +lexSymbols :: KeywordEnv -> 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' | '\\f')+ -> skip;" + , "// Escapable sequences" + , "fragment" + , "Escapable : ('\"' | '\\\\' | 'n' | 't' | 'r' | 'f');" + , "ErrorToken : . ;" + , ifString stringmodes + , ifChar charmodes + ] + where + ifC cat s = if isUsedCat cf (TokenCat 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 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) + <> "'" diff --git a/source/src/BNFC/Backend/Antlr/CFtoAntlr4Parser.hs b/source/src/BNFC/Backend/Antlr/CFtoAntlr4Parser.hs new file mode 100644 index 00000000..1d77f37b --- /dev/null +++ b/source/src/BNFC/Backend/Antlr/CFtoAntlr4Parser.hs @@ -0,0 +1,159 @@ +{-# LANGUAGE LambdaCase #-} + +module BNFC.Backend.Antlr.CFtoAntlr4Parser ( cf2AntlrParse, antlrRuleLabel, makeLeftRecRule ) where + +import Data.Foldable ( toList ) +import Data.Maybe + +import BNFC.CF +import BNFC.Options ( RecordPositions(..) ) +import BNFC.Utils ( (+++), applyWhen ) + +import BNFC.Backend.Antlr.Utils +import BNFC.Backend.Common.NamedVariables + +-- Type declarations + +-- | A definition of a non-terminal by all its rhss +data PDef = PDef + { _pdNT :: Maybe String + -- ^ If given, the name of the lhss. Usually computed from 'pdCat'. + , _pdCat :: Cat + -- ^ The category to parse. + , _pdAlts :: [(Pattern, Maybe Fun)] + -- ^ The possible rhss with actions. If 'null', skip this 'PDef'. + -- Where 'Nothing', skip ANTLR rule label. + } +type Rules = [PDef] +type Pattern = String + +-- | Creates the ANTLR parser grammar for this CF. +--The environment comes from CFtoAntlr4Lexer +cf2AntlrParse :: String -> String -> CF -> RecordPositions -> KeywordEnv -> String +cf2AntlrParse lexerName parserName cf _ env = unlines + [ header + , tokens + , "" + -- Generate start rules + , prRules $ map entrypoint $ toList $ allEntryPoints cf + -- Generate regular rules + , prRules $ rulesForAntlr4 cf env + ] + + where + header :: String + header = unlines + [ "// Parser definition for use with ANTLRv4" + , "parser grammar" +++ parserName ++ ";" + ] + tokens :: String + tokens = unlines + [ "options {" + , " tokenVocab =" +++ lexerName ++ ";" + , "}" + ] + +-- | Generate start rule to help ANTLR. +-- +-- @start_X : X EOF +-- +entrypoint :: Cat -> PDef +entrypoint cat = + PDef (Just nt) cat [(pat, fun)] + where + nt = firstLowerCase $ startSymbol $ identCat cat + pat = catToNT cat +++ "EOF" + fun = Nothing + +--The following functions are a (relatively) straightforward translation +--of the ones in CFtoHappy.hs +rulesForAntlr4 :: CF -> KeywordEnv -> Rules +rulesForAntlr4 cf env = map mkOne getrules + where + getrules = ruleGroups cf + mkOne (cat,rules) = constructRule 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 :: CF -> KeywordEnv -> [Rule] -> NonTerminal -> PDef +constructRule cf env rules nt = + PDef Nothing nt $ + [ ( p, Just label ) + | (index, r0) <- zip [1..] rules + , let r = makeLeftRecRule cf r0 + , let p = generatePattern index env r + , let label = wpThing (funRule r) + ] + +makeLeftRecRule :: CF -> Rule -> Rule +makeLeftRecRule cf rule = applyWhen canBeLeftRecursive revSepListRule rule + where + canBeLeftRecursive = isConsFun (funRule rule) && elem (valCat rule) (cfgReversibleCats cf) + +-- | Generate patterns and a set of metavariables indicating +-- where in the pattern the non-terminal +-- >>> generatePatterns 2 [] $ npRule "myfun" (Cat "A") [] Parsable +-- (" /* empty */ ",[]) +-- >>> generatePatterns 3 [("def", "_SYMB_1")] $ npRule "myfun" (Cat "A") [Right "def", Left (Cat "B")] Parsable +-- ("_SYMB_1 p_3_2=b",[("p_3_2",B)]) +generatePattern :: Int -> KeywordEnv -> Rule -> Pattern +generatePattern ind env r = + case rhsRule r of + [] -> " /* empty */ " + its -> unwords $ mapMaybe (uncurry mkIt) nits + where + nits = zip [1 :: Int ..] its + var i = "p_" ++ show ind ++"_"++ show i -- TODO: is ind needed for ANTLR? + mkIt i = \case + Left c -> Just $ var i ++ "=" ++ catToNT c + Right s -> lookup s env + +catToNT :: Cat -> String +catToNT = \case + TokenCat "Ident" -> "IDENT" + TokenCat "Integer" -> "INTEGER" + TokenCat "Char" -> "CHAR" + TokenCat "Double" -> "DOUBLE" + TokenCat "String" -> "STRING" + c | isTokenCat c -> identCat c + | otherwise -> firstLowerCase $ getRuleName $ identCat c + +-- | Puts together the pattern and actions and returns a string containing all +-- the rules. +prRules :: Rules -> String +prRules = concatMap $ \case + + -- No rules: skip. + PDef _mlhs _nt [] -> "" + + -- At least one rule: print! + PDef mlhs nt rhss -> unlines $ concat + + -- The definition header: lhs and type. + [ [ unwords [fromMaybe nt' mlhs] + ] + -- The first rhs. + , alternative " :" $ head indexedRhss + -- The other rhss. + , concatMap (alternative " |") $ tail indexedRhss + -- The definition footer. + , [ " ;" ] + ] + where + alternative sep ((p, label), idx) = unwords [ sep , p ] : [ unwords [ " #" , antlrRuleLabel nt l idx] | Just l <- [label] ] + indexedRhss = zipWith (\rule idx -> if (maybe False isCoercion (snd rule)) then (rule, Just idx) else (rule, Nothing)) rhss [1..] + + catid = identCat nt + nt' = getRuleName $ firstLowerCase catid + +-- we use rule's index as prefix for ANTLR label +-- in order to avoid name collisions for coercion types +antlrRuleLabel :: Cat -> Fun -> Maybe Integer -> String +antlrRuleLabel cat fnc int + | isNilFun fnc = catid ++ "_Empty" + | isOneFun fnc = catid ++ "_AppendLast" + | isConsFun fnc = catid ++ "_PrependFirst" + | isCoercion fnc = "Coercion_" ++ catid ++ maybe "" (("_" ++) . show) int + | otherwise = funName fnc + where + catid = identCat cat diff --git a/source/src/BNFC/Backend/Antlr/RegToAntlrLexer.hs b/source/src/BNFC/Backend/Antlr/RegToAntlrLexer.hs new file mode 100644 index 00000000..3a1c50bd --- /dev/null +++ b/source/src/BNFC/Backend/Antlr/RegToAntlrLexer.hs @@ -0,0 +1,90 @@ +module BNFC.Backend.Antlr.RegToAntlrLexer (printRegJLex, escapeCharInSingleQuotes) where + +-- modified from RegToJLex.hs + +import Data.Char (ord) +import Numeric (showHex) + +import BNFC.Abs + +-- 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] + +-- | Print char according to ANTLR regex format. +escapeChar :: [Char] -> Char -> String +escapeChar reserved x + | x `elem` reserved = '\\' : [x] + | i >= 65536 = "\\u{" ++ h ++ "}" + | i >= 256 || i < 32 = "\\u" ++ replicate (4 - length h) '0' ++ h + | otherwise = [x] -- issue #329, don't escape in the usual way! + where + i = ord x + h = showHex i "" + +-- | Escape character for use inside single quotes. +escapeCharInSingleQuotes :: Char -> String +escapeCharInSingleQuotes = escapeChar ['\'','\\'] + +-- The ANTLR definition of what can be in a [char set] is here: +-- https://github.com/antlr/antlr4/blob/master/doc/lexer-rules.md#lexer-rule-elements +-- > The following escaped characters are interpreted as single special characters: +-- > \n, \r, \b, \t, \f, \uXXXX, and \u{XXXXXX}. +-- > To get ], \, or - you must escape them with \. + +-- | Escape character for use inside @[char set]@. +escapeInCharSet :: Char -> String +escapeInCharSet = escapeChar [ ']', '\\', '-' ] + +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]) + RMinus reg0 REps -> prt i reg0 -- REps is identity for set difference + RMinus RAny (RChar c) + -> ["~'", escapeCharInSingleQuotes c, "'"] + RMinus RAny (RAlts str) + -> concat [["~["], map escapeInCharSet str ,["]"]] + RMinus _ _ -> error "Antlr does not support general set difference" + RStar reg -> prt 3 reg ++ ["*"] + RPlus reg -> prt 3 reg ++ ["+"] + ROpt reg -> prt 3 reg ++ ["?"] + REps -> [""] + RChar c -> ["'", escapeCharInSingleQuotes c, "'"] + RAlts str -> concat [ ["["], map escapeInCharSet str, ["]"] ] + RSeqs str -> prPrec i 2 $ map show str + RDigit -> ["DIGIT"] + RLetter -> ["LETTER"] + RUpper -> ["CAPITAL"] + RLower -> ["SMALL"] + RAny -> ["[\\u0000-\\u00FF]"] diff --git a/source/src/BNFC/Backend/Antlr/Utils.hs b/source/src/BNFC/Backend/Antlr/Utils.hs new file mode 100644 index 00000000..c5869d0e --- /dev/null +++ b/source/src/BNFC/Backend/Antlr/Utils.hs @@ -0,0 +1,65 @@ +{-# LANGUAGE RecordWildCards #-} + +module BNFC.Backend.Antlr.Utils where + +import Prelude +import System.FilePath ((<.>)) + +import BNFC.Utils ((+++)) +import BNFC.Options as Options + +getRuleName :: String -> String +getRuleName z = if z == "grammar" then z ++ "_" else z + +-- | Make a new entrypoint NT for an existing NT. + +startSymbol :: String -> String +startSymbol = ("Start_" ++) + +dotG4 :: String -> String +dotG4 = (<.> "g4") + +-- Left | Middle | Rigth +data Either3 a b c = L a | M b | R c + +-- There are three variants of ANTLRv4 options: +-- "-OptName", "-OptName=OptValue", "-OptName Optvalue" +type OptionType = Either3 Bool String String + +getAntlrOptions :: SharedOptions -> String +getAntlrOptions Options{..} = unwords $ map ("-" ++) parsedOpts + where + parsedOpts = getAntlrOptions' + [ ("listener", L listener) + , ("no-listener", L $ not listener) + , ("visitor", L visitor) + , ("no-visitor", L $ not visitor) + , ("Werror", L wError) + , ("Dlanguage", M $ parseAntlrTarget dLanguage) + , ("Xlog", L xlog) + , ("XdbgST", L xDbgST) + , ("XdbgSTWait", L xDbgSTWait) + , ("atn", L atn) + ] + +getAntlrOptions' :: [(String, OptionType)] -> [String] +getAntlrOptions' [] = [] +getAntlrOptions' (opt : opts) = case opt of + (_, L False) -> otherOpts + (optName, L True) -> optName : otherOpts + (optName, M value) -> (optName ++ "=" ++ value) : otherOpts + (optName, R value) -> (optName +++ value) : otherOpts + where + otherOpts = getAntlrOptions' opts + +parseAntlrTarget :: AntlrTarget -> String +parseAntlrTarget Java = "Java" +parseAntlrTarget CPP = "Cpp" +parseAntlrTarget CSharp = "CSharp" +parseAntlrTarget JS = "JavaScript" +parseAntlrTarget TS = "TypeScript" +parseAntlrTarget Dart = "Dart" +parseAntlrTarget Python3 = "Python3" +parseAntlrTarget PHP = "PHP" +parseAntlrTarget Go = "Go" +parseAntlrTarget Swift = "Swift" diff --git a/source/src/BNFC/Backend/Common/NamedVariables.hs b/source/src/BNFC/Backend/Common/NamedVariables.hs index a76b7261..ba5e3f67 100644 --- a/source/src/BNFC/Backend/Common/NamedVariables.hs +++ b/source/src/BNFC/Backend/Common/NamedVariables.hs @@ -53,7 +53,7 @@ This is what this module does. module BNFC.Backend.Common.NamedVariables where import Control.Arrow (left, (&&&)) -import Data.Char (toLower) +import Data.Char (toLower, toUpper) import Data.Either (lefts) import Data.List (nub) import Data.Map (Map) @@ -157,3 +157,7 @@ showNum n = if n == 0 then "" else show n firstLowerCase :: String -> String firstLowerCase "" = "" firstLowerCase (a:b) = toLower a:b + +firstUpperCase :: String -> String +firstUpperCase "" = "" +firstUpperCase (a:b) = toUpper a:b \ No newline at end of file diff --git a/source/src/BNFC/Backend/Dart.hs b/source/src/BNFC/Backend/Dart.hs new file mode 100644 index 00000000..556148f4 --- /dev/null +++ b/source/src/BNFC/Backend/Dart.hs @@ -0,0 +1,179 @@ +{-# LANGUAGE RecordWildCards #-} + +module BNFC.Backend.Dart ( makeDart ) where + +import Text.PrettyPrint ( text, vcat, render, nest ) + +import Prelude hiding ((<>)) +import System.FilePath ((), pathSeparator) +import System.Directory ( createDirectoryIfMissing ) +import Data.Char (toLower) + +import BNFC.Backend.Base (MkFiles, mkfile,liftIO) +import BNFC.CF (CF, getAbstractSyntax, firstEntry, catToStr, identCat, normCat ) +import BNFC.Options (SharedOptions (Options, inPackage, lang, optMake, dLanguage, antlrOpts, outDir), AntlrTarget (Dart)) +import BNFC.Utils (mkName, NameStyle (SnakeCase), replace, (+.+), (+++)) +import BNFC.Backend.Common.Makefile as MakeFile +import BNFC.Backend.Common.NamedVariables (firstUpperCase, firstLowerCase) +import BNFC.Backend.Antlr (makeAntlr', DirectoryOptions (DirectoryOptions, baseDirectory, nameStyle)) +import BNFC.Backend.Dart.CFtoDartAST ( cf2DartAST ) +import BNFC.Backend.Dart.CFtoDartBuilder ( cf2DartBuilder ) +import BNFC.Backend.Dart.CFtoDartPrinter ( cf2DartPrinter ) +import BNFC.Backend.Dart.CFtoDartSkeleton ( cf2DartSkeleton ) +import BNFC.Backend.Dart.Common ( indent, buildVariableTypeFromDartType, cat2DartType, cat2DartClassName ) + +makeDart :: SharedOptions -> CF -> MkFiles () +makeDart opts@Options{..} cf = do + let dirBase = replace '.' pathSeparator $ packageName + langBase = dirBase (langName ++ "_generated") + libLang = langBase "lib" + srcLang = libLang "src" + libBase = dirBase "lib" + binBase = dirBase "bin" + directoryOptions = DirectoryOptions{baseDirectory = Just srcLang, nameStyle = Just SnakeCase} + + -- Generates files in an incorrect place + + makeAntlr' (opts {dLanguage = Dart, optMake = Nothing}) cf directoryOptions + MakeFile.mkMakefile optMake $ makefileContent srcLang + + mkfile (srcLang "ast.dart") makeDartComment astContent + mkfile (srcLang "builder.dart") makeDartComment builderContent + mkfile (srcLang "pretty_printer.dart") makeDartComment printerContent + mkfile (libLang (langName ++ "_generated.dart")) makeDartComment exportsContent + mkfile (langBase "pubspec.yaml") makeDartCommentYaml + $ pubspecContent + (langName ++ "_generated") + ("A module with the AST, Pretty-Printer and AST-builder for" +++ langName) + [] + mkfile (libBase "test.dart") makeDartComment testContent + mkfile (libBase "skeleton.dart") makeDartComment skeletonContent + mkfile (binBase "main.dart") makeDartComment mainContent + mkfile (dirBase "pubspec.yaml" ) makeDartCommentYaml + $ pubspecContent + (langName ++ "_example") + ("A simple project for" +++ langName) + [ langName ++ "_generated:", " path:" +++ langName ++ "_generated" ] + + where + astContent = cf2DartAST (firstUpperCase langName) cf + builderContent = cf2DartBuilder (firstUpperCase langName) cf + printerContent = cf2DartPrinter (firstUpperCase langName) cf + skeletonContent = cf2DartSkeleton (firstUpperCase langName) cf importLangName + exportsContent = unlines + [ "export 'src/ast.dart';" + , "export 'src/builder.dart';" + , "export 'src/pretty_printer.dart';" + , "export 'src/" ++ langName ++ "_lexer.dart';" + , "export 'src/" ++ langName ++ "_parser.dart';" ] + testContent = + let + firstCat = firstEntry cf + varType = buildVariableTypeFromDartType $ cat2DartType (firstUpperCase langName) firstCat + varName = cat2DartClassName langName firstCat + rawVarName = firstLowerCase $ identCat $ normCat firstCat + in unlines ( + [ "import 'package:antlr4/antlr4.dart';" + , "import 'package:fast_immutable_collections/fast_immutable_collections.dart';" + , importLangName + , "import 'skeleton.dart';" + , "class Test {" + , " Future run(List arguments) async {" ] + ++ ( indent 2 + [ "final input = await InputStream.fromString(arguments[0]);" + , "final lexer =" +++ langName ++ "_lexer(input);" + , "final tokens = CommonTokenStream(lexer);" + , "final parser =" +++ langName ++ "_parser(tokens);" + , "parser.addErrorListener(DiagnosticErrorListener());" + , "final output = build" ++ varName ++ "(parser." ++ rawVarName ++ "());" + , "print('\"Parse Successful!\"\\n');" + , "print('\"[Abstract Syntax]\"\\n');" + , "print('${output?.print}\\n');" + , "print('\"[Linearized Tree]\"\\n');" + , "print(switch (output) {" + , " null => '" ++ varType ++ " is null'," + , " " ++ varType ++ " p => interpret" ++ varName ++ "(p)," + , "});" + ] ) + ++ [ " }", "}" ] ) + mainContent = unlines + [ "import '../lib/test.dart';" + , "void main(List args) {" + , " final test = Test();" + , " test.run(args);" + , "}" ] + packageName = maybe id (+.+) inPackage $ mkName [] SnakeCase lang + langName = firstLowerCase $ mkName [] SnakeCase lang + importLangName = "import 'package:" ++ langName ++ "_generated/" ++ langName ++ "_generated.dart';" + + pubspecContent moduleName desc deps = unlines ( + [ "name:" +++ moduleName + , "description:" +++ desc + , "version: 1.0.0" + , "publish_to: 'none'" + , "environment:" + , " sdk: ^3.4.0" + , "dependencies:" + , " antlr4: ^4.13.1" + , " fast_immutable_collections: ^10.2.2" + ] ++ (indent 1 deps) ++ [ "dev_dependencies:" + , " lints: ^4.0.0" ]) + + lexerClassName = lang ++ "GrammarLexer" + parserClassName = lang ++ "GrammarParser" + + makeVars x = [MakeFile.mkVar n v | (n,v) <- x] + makeRules x = [MakeFile.mkRule tar dep recipe | (tar, dep, recipe) <- x] + + makefileVars = vcat $ makeVars + [("LANG", langName) + , ("LEXER_NAME", langName ++ "_lexer") + , ("PARSER_NAME", langName ++ "_parser") + , ("ANTLR4", "java -Xmx500M -cp \"/usr/local/lib/antlr-4.13.1-complete.jar:$CLASSPATH\" org.antlr.v4.Tool") + ] + + refVarInSrc srcLang refVar = srcLang MakeFile.refVar refVar + + rmFile :: (String -> String) -> String -> String -> String + rmFile refSrcVar refVar ext = "rm -f" +++ refSrcVar refVar ++ ext + + makefileRules refSrcVar = + let rmInSrc = rmFile refSrcVar + in vcat $ makeRules + [ (".PHONY", ["all", "clean", "remove"], []) + , ("all", [MakeFile.refVar "LANG"], []) + , ("lexer" + , [refSrcVar "LEXER_NAME" ++ ".g4"] + , [MakeFile.refVar "ANTLR4" +++ "-Dlanguage=Dart" +++ refSrcVar "LEXER_NAME" ++ ".g4"]) + , ("parser" + , [refSrcVar "PARSER_NAME" ++ ".g4"] + , [MakeFile.refVar "ANTLR4" +++ "-Dlanguage=Dart" +++ "-no-listener" +++ "-no-visitor" +++ refSrcVar "PARSER_NAME" ++ ".g4"]) + , ("install-deps-external" + , [MakeFile.refVar "LANG" "pubspec.yaml"] + , ["cd" +++ (MakeFile.refVar "LANG") ++ "; dart pub get"]) + , ("install-deps-internal" + , [MakeFile.refVar "LANG" (MakeFile.refVar "LANG" ++ "_generated") "pubspec.yaml"] + , ["cd" +++ (MakeFile.refVar "LANG" (MakeFile.refVar "LANG" ++ "_generated")) ++ "; dart pub get"]) + , (MakeFile.refVar "LANG", ["lexer", "parser", "clean", "install-deps-external", "install-deps-internal"], []) + , ("clean", [], + [ + rmInSrc "LEXER_NAME" ".interp" + , rmInSrc "LEXER_NAME" ".tokens" + , rmInSrc "PARSER_NAME" ".interp" + , rmInSrc "PARSER_NAME" ".tokens" + , rmInSrc "LEXER_NAME" ".g4" + , rmInSrc "PARSER_NAME" ".g4" + ]) + , ("remove", [], ["rm -rf" +++ MakeFile.refVar "LANG"]) + ] + + makefileContent srcLang _ = vcat [makefileVars, "", makefileRules $ refVarInSrc srcLang, ""] + +makeDartComment :: String -> String +makeDartComment = ("// Dart " ++) + +makeDartCommentYaml :: String -> String +makeDartCommentYaml = ("# Dart" ++) + +toLowerCase :: String -> String +toLowerCase = map toLower diff --git a/source/src/BNFC/Backend/Dart/CFtoDartAST.hs b/source/src/BNFC/Backend/Dart/CFtoDartAST.hs new file mode 100644 index 00000000..74e94038 --- /dev/null +++ b/source/src/BNFC/Backend/Dart/CFtoDartAST.hs @@ -0,0 +1,139 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Dart.CFtoDartAST (cf2DartAST) where + +import Data.Maybe ( mapMaybe ) + +import BNFC.CF +import BNFC.Utils ( (+++) ) + +import BNFC.Backend.Common.NamedVariables ( UserDef ) +import BNFC.Backend.Dart.Common + +-- Produces abstract data types in Dart +cf2DartAST :: String -> CF -> String +cf2DartAST langName cf = + let userTokens = [ n | (n,_) <- tokenPragmas cf ] + in unlines + $ imports -- import some libraries if needed + ++ characterTypedef + ++ generateTokens userTokens + ++ concatMap prData rules -- generate user-defined types + where + rules = getAbstractSyntax cf + imports = + [ "import 'pretty_printer.dart' as pp;" + , "import 'package:fast_immutable_collections/fast_immutable_collections.dart';" ] + characterTypedef = [ "typedef Character = String;" ] + censorName' = censorName langName + str2DartClassName' = str2DartClassName langName + cat2DartClassName' = cat2DartClassName langName + getVars' = getVars langName + + + generateTokens :: [UserDef] -> [String] + generateTokens = map $ \token -> + let name = censorName' token + in "typedef" +++ name +++ "= String;" + + + -- | Generates a category class, and classes for all its rules. + prData :: Data -> [String] + prData (cat, rules) = + categoryClass ++ mapMaybe (prRule cat) rules + where + funs = map fst rules + categoryClass + | catToStr cat `elem` funs || isList cat = [] -- the category is also a function or a list + | otherwise = + let name = cat2DartClassName' cat + in + [ "sealed class" +++ name +++ "with pp.Printable {" + , " @override" + , " String get print => pp.print" ++ name ++ "(this);" + , "}" ] + + + -- | Generates classes for a rule, depending on what type of rule it is. + prRule :: Cat -> (Fun, [Cat]) -> Maybe (String) + prRule cat (fun, cats) + | isNilFun fun || + isOneFun fun || + isConsFun fun = Nothing -- these are not represented in the Absyn + | otherwise = -- a standard rule + let + className = str2DartClassName' fun + vars = getVars' cats + in Just . unlines $ + [ unwords [ "class", className, extending, "with pp.Printable {" ] ] ++ + concatMap (indent 1) [ + prInstanceVariables vars, + prConstructor className vars, + prEquals className vars, + prHashCode vars, + prPrettyPrint className + ] ++ [ "}" ] + where + extending + | fun == catToStr cat = "" + | otherwise = "extends" +++ cat2DartClassName' cat + + + -- Override the equality `==` + prEquals :: String -> [DartVar] -> [String] + prEquals className variables = [ + "@override", + "bool operator ==(Object o) =>", + " o is" +++ className +++ "&&", + " o.runtimeType == runtimeType" ++ + (if null variables then ";" else " &&") + ] ++ checkChildren + where + checkChildren = generateEqualities variables + generateEqualities [] = [] + generateEqualities (variable:rest) = + let name = buildVariableName variable + in [ + " " ++ name +++ "==" +++ "o." ++ name ++ + (if null rest then ";" else " &&") + ] ++ generateEqualities rest + + + -- Override the hashCode, combining all instance variables + prHashCode :: [DartVar] -> [String] + prHashCode vars = [ + "@override", + "int get hashCode => Object.hashAll([" ++ + concatMap variableHash vars ++ + "]);" + ] + where + variableHash variable = buildVariableName variable ++ ", " + + + -- Generate variable definitions for the class + prInstanceVariables :: [DartVar] -> [String] + prInstanceVariables vars = map variableLine vars + where + variableLine variable = + let vType = buildVariableType variable + vName = buildVariableName variable + in "final" +++ vType +++ vName ++ ";" + + + -- Generate the class constructor + prConstructor :: String -> [DartVar] -> [String] + prConstructor className vars = + [ className ++ "(" ++ variablesAssignment ++ ");" ] + where + variablesAssignment + | null vars = "" + | otherwise = "{" ++ (concatMap assignment vars) ++ "}" + assignment variable = "required this." ++ buildVariableName variable ++ ", " + + prPrettyPrint :: String -> [String] + prPrettyPrint name = [ + "@override", + "String get print => pp.print" ++ name ++ "(this);" ] diff --git a/source/src/BNFC/Backend/Dart/CFtoDartBuilder.hs b/source/src/BNFC/Backend/Dart/CFtoDartBuilder.hs new file mode 100644 index 00000000..153974d2 --- /dev/null +++ b/source/src/BNFC/Backend/Dart/CFtoDartBuilder.hs @@ -0,0 +1,244 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Dart.CFtoDartBuilder (cf2DartBuilder) where + +import BNFC.CF +import BNFC.Backend.Common.NamedVariables (firstLowerCase) +import BNFC.Backend.Dart.Common +import BNFC.Backend.Antlr.CFtoAntlr4Parser (makeLeftRecRule) +import BNFC.Utils ( (+++) ) +import Data.List ( intercalate, find ) +import Data.Either ( isLeft ) + +cf2DartBuilder :: String -> CF -> String +cf2DartBuilder lang cf = + let userTokens = [ n | (n,_) <- tokenPragmas cf ] + in + unlines $ + imports lang ++ + helperFunctions ++ + map buildUserToken userTokens ++ + concatMap generateBuilders rules + where + leftRecRuleMaker = (makeLeftRecRule cf) + rules = map + (\(cat, rules) -> (cat, (map leftRecRuleMaker rules))) $ ruleGroups cf + imports lang = + [ "import 'package:antlr4/antlr4.dart' show Token;" + , "import 'package:fast_immutable_collections/fast_immutable_collections.dart' show IList;" + , "import 'ast.dart';" + , "import '" ++ (firstLowerCase lang) ++ "_parser.dart'; // fix this line depending on where the stellaParser is being lcated" ] + helperFunctions = + [ "int? buildInteger(Token? t) => t?.text != null ? int.tryParse(t!.text!) : null;" + , "double? buildDouble(Token? t) => t?.text != null ? double.tryParse(t!.text!) : null;" + , "String? buildString(Token? t) => t?.text;" + , "String? buildChar(Token? t) => t?.text;" + , "String? buildIdent(Token? t) => t?.text;" ] + buildUserToken token = + let name = censorName lang token + in name ++ "? build" ++ name ++ "(Token? t) => t?.text;" + str2DartClassName' = str2DartClassName lang + getVars' = getVars lang + cat2DartClassName' = cat2DartClassName lang + cat2DartType' = cat2DartType lang + + generateBuilders :: (Cat, [Rule]) -> [String] + generateBuilders (cat, rawRules) = + let + numeratedRawRules = zip [1..] rawRules + in runtimeTypeMapping numeratedRawRules + ++ (concatMap (uncurry generateConcreteMapping) numeratedRawRules) + where + funsFrom = map (\(_, rule) -> wpThing $ funRule rule) + runtimeTypeMapping numeratedRawRules + | (catToStr cat) `elem` (funsFrom numeratedRawRules) = [] + | otherwise = generateRuntimeTypeMapping cat [ + (index, wpThing $ funRule rule, rhsRule rule) | + (index, rule) <- numeratedRawRules ] + + + reformatRule :: Rule -> (String, [Cat]) + reformatRule rule = (wpThing $ funRule rule, [normCat c | Left c <- rhsRule rule ]) + + + generateRuntimeTypeMapping :: Cat -> [(Int, String, [Either Cat String])] -> [String] + generateRuntimeTypeMapping cat rules = + let ctxName = upperFirst $ identCat $ normCat cat + astName = buildVariableTypeFromDartType $ cat2DartType' cat + prec = case precCat cat of + 0 -> "" + x -> show x + precedencedName = ctxName ++ prec + in + [ astName ++ "?" +++ "build" ++ precedencedName ++ "(" + ++ (contextName precedencedName) ++ "?" +++ "ctx" ++ ") {" ] + ++ indent 1 ( (map (buildChild precedencedName) rules) + ++ ["return null;"] ) + ++ [ "}" ] + where + buildUniversalChild name fun arg = + "if (ctx is" +++ name ++ ") return build" ++ fun ++ "(" ++ arg ++ ");" + buildChild className (index, name, rhs) = case (antlrListSuffix name) of + "" -> if (isCoercion name) + then + let firstCat = find + (\(_, value) -> isLeft value) + $ zip [1..] rhs + (coercionType, ind2) = case (firstCat) of + Just (i, Left cat) -> + ( let precStr = case precCat cat of + 0 -> "" + x -> show x + catName = upperFirst $ identCat $ normCat cat + in catName ++ precStr + , show i ) + otherwise -> (className, "") -- error, no category in the coercion rule + lineIndex = show index + argument = "p_" ++ lineIndex ++ "_" ++ ind2 + in + buildUniversalChild + ("Coercion_" ++ contextName (className ++ "_" ++ lineIndex)) + coercionType + ("ctx." ++ argument) + else + buildUniversalChild + (contextName $ str2AntlrClassName name) + name + -- (str2DartClassName' name) + "ctx" + suffix -> buildUniversalChild + (contextName (className ++ "_" ++ suffix)) + (className ++ suffix) + "ctx" + + + generateConcreteMapping :: Int -> Rule -> [String] + generateConcreteMapping index rule = + generateConcreteMappingHelper index rule $ reformatRule rule + + + generateConcreteMappingHelper :: Int -> Rule -> (String, [Cat]) -> [String] + generateConcreteMappingHelper index rule (fun, cats) + | isCoercion fun = [] + | otherwise = + let + (typeName, className, ctxName) = + if (isNilFun fun || + isOneFun fun || + isConsFun fun) + then + let cat = valCat rule + prec = case (precCat cat) of + 0 -> "" + i -> show i + ctxName = (++ prec) $ upperFirst $ identCat $ normCat cat + suffix = antlrListSuffix fun + precedencedName = ctxName ++ suffix + suffixedCtxName = contextName (ctxName ++ "_" ++ suffix) + astName = buildVariableTypeFromDartType $ cat2DartType' cat + in (astName, precedencedName, suffixedCtxName) + else + let name = str2DartClassName' fun + ctxName = contextName $ str2AntlrClassName fun + in (name, fun, ctxName) + vars = getVars' cats + in [ + typeName ++ "?" +++ "build" ++ className ++ "(" ++ ctxName ++ "?" +++ "ctx) {" + ] ++ ( + indent 1 $ + (generateArguments index rule $ zip vars cats) ++ + (generateNullCheck vars) ++ + (generateReturnStatement fun vars typeName) + ) ++ [ + "}" + ] + where + generateReturnStatement :: Fun -> [DartVar] -> String -> [String] + generateReturnStatement fun vars typeName + | isNilFun fun = ["return IList();"] + | isOneFun fun = generateOneArgumentListReturn vars + | isConsFun fun = generateTwoArgumentsListReturn vars + | otherwise = [ "return" +++ typeName ++ "(" ] ++ + (indent 1 $ generateArgumentsMapping vars ) ++ [");"] + + + generateArguments :: Int -> Rule -> [(DartVar, Cat)] -> [String] + generateArguments index r vars = + case rhsRule r of + [] -> [] + its -> traverseRule index 1 its vars [] + + + traverseRule :: Int -> Int -> [Either Cat String] -> [(DartVar, Cat)] -> [String] -> [String] + traverseRule _ _ _ [] lines = lines + traverseRule _ _ [] _ lines = lines + traverseRule ind1 ind2 (terminal:restTs) (var@(varDart, varCat):restVars) lines = + case terminal of + Left cat -> + let lhs = buildVariableName varDart + rhs = buildArgument + (precCat cat) + (upperFirst $ identCat $ normCat varCat) + -- (cat2DartClassName' varCat) + field + in [ "final" +++ lhs +++ "=" +++ rhs ++ ";" ] + ++ traverseRule ind1 (ind2 + 1) restTs restVars lines + Right _ -> traverseRule ind1 (ind2 + 1) restTs (var:restVars) lines + where + field = "ctx?.p_" ++ show ind1 ++ "_" ++ show ind2 + buildArgument :: Integer -> String -> String -> String + buildArgument prec typeName name = + let precedence = if prec == 0 then "" else show prec + in "build" ++ upperFirst typeName ++ precedence ++ "(" ++ name ++ ")" + -- buildArgument prec (_, _) typeName name = + -- let precedence = if prec == 0 then "" else show prec + -- in "buildList" ++ upperFirst typeName ++ precedence ++ "(" ++ name ++ ")" + + + generateNullCheck :: [DartVar] -> [String] + generateNullCheck [] = [] + generateNullCheck vars = + [ "if (" ] ++ + (indent 1 [ intercalate " || " $ map condition vars ]) ++ + [ ") {" ] ++ + (indent 1 [ "return null;" ]) ++ + [ "}" ] + where + condition :: DartVar -> String + condition var = buildVariableName var +++ "==" +++ "null" + + + generateArgumentsMapping :: [DartVar] -> [String] + generateArgumentsMapping vars = map mapArgument vars + where + mapArgument variable = + let name = buildVariableName variable + in name ++ ":" +++ name ++ "," + + + generateOneArgumentListReturn :: [DartVar] -> [String] + generateOneArgumentListReturn (v:_) = + ["return IList([" ++ buildVariableName v ++ "]);"] + + + generateTwoArgumentsListReturn :: [DartVar] -> [String] + generateTwoArgumentsListReturn (x:y:_) = + let (a, b) = putListSecond x y + in ["return IList([" ++ buildVariableName a ++ ", ..." ++ buildVariableName b ++ ",]);"] + where + putListSecond x@((0,_),_) y = (x, y) + putListSecond x y = (y, x) + + + contextName :: String -> String + contextName className = className ++ "Context" + + + antlrListSuffix :: Fun -> String + antlrListSuffix fun + | isNilFun fun = "Empty" + | isOneFun fun = "AppendLast" + | isConsFun fun = "PrependFirst" + | otherwise = "" \ No newline at end of file diff --git a/source/src/BNFC/Backend/Dart/CFtoDartPrinter.hs b/source/src/BNFC/Backend/Dart/CFtoDartPrinter.hs new file mode 100644 index 00000000..d11e5ef5 --- /dev/null +++ b/source/src/BNFC/Backend/Dart/CFtoDartPrinter.hs @@ -0,0 +1,308 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Dart.CFtoDartPrinter (cf2DartPrinter) where + +import BNFC.CF +import BNFC.Backend.Dart.Common +import BNFC.Utils ( (+++) ) +import Data.Maybe ( mapMaybe ) +import Data.List ( intercalate, find ) +import Data.Either ( isLeft ) + +cf2DartPrinter :: String -> CF -> String +cf2DartPrinter langName cf = + let userTokens = [ n | (n,_) <- tokenPragmas cf ] + in + unlines $ + imports ++ + helperFunctions ++ + stringRenderer ++ + (concatMap buildUserToken userTokens) ++ + (concatMap generateRulePrinters $ getAbstractSyntax cf) ++ + (concatMap generateLabelPrinters $ ruleGroupsInternals cf ) + where + str2DartClassName' = str2DartClassName langName + getVars' = getVars langName + cat2DartClassName' = cat2DartClassName langName + cat2DartType' = cat2DartType langName + + imports :: [String] + imports = + [ "import 'ast.dart' as ast;" + , "import 'package:fast_immutable_collections/fast_immutable_collections.dart';" ] + + helperFunctions :: [String] + helperFunctions = + [ "sealed class Token {}" + , "" + , "class Space extends Token {}" + , "" + , "class NewLine extends Token {" + , " int indentDifference;" + , " NewLine.indent(this.indentDifference);" + , " NewLine() : indentDifference = 0;" + , " NewLine.nest() : indentDifference = 1;" + , " NewLine.unnest() : indentDifference = -1;" + , "}" + , "" + , "class Text extends Token {" + , " String text;" + , " Text(this.text);" + , "}" ] + + stringRenderer :: [String] + stringRenderer = + [ "class StringRenderer {" + , " // Change this value if you want to change the indentation length" + , " static const _indentInSpaces = 2;" + , "" + , " String print(Iterable tokens) => tokens" + , " .map((element) => element.trim())" + , " .fold(IList(), _render)" + , " .fold(IList<(int, IList)>(), _split)" + , " .map((line) => (line.$1, line.$2.map(_tokenToString).join()))" + , " .fold(IList<(int, String)>(), _convertIndentation)" + , " .map(_addIndentation)" + , " .join('\\n');" + , "" + , " IList<(int, IList)> _split(" + , " IList<(int, IList)> lists," + , " Token token," + , " ) =>" + , " switch (token) {" + , " NewLine nl => lists.add((" + , " nl.indentDifference," + , " IList()," + , " ))," + , " _ => lists.isEmpty" + , " ? IList([" + , " (0, IList([token]))" + , " ])" + , " : lists.put(" + , " lists.length - 1," + , " (lists.last.$1, lists.last.$2.add(token))," + , " )," + , " };" + , "" + , " String _tokenToString(Token t) => switch (t) {" + , " Text t => t.text," + , " Space _ => ' '," + , " _ => ''," + , " };" + , "" + , " IList<(int, String)> _convertIndentation(" + , " IList<(int, String)> lines," + , " (int, String) line," + , " ) =>" + , " lines.add((" + , " line.$1 + (lines.lastOrNull?.$1 ?? 0)," + , " line.$2," + , " ));" + , "" + , " String _addIndentation((int, String) indentedLine) =>" + , " ' ' * (_indentInSpaces * indentedLine.$1) + indentedLine.$2;" + , "" + , " // This function is supposed to be edited" + , " // in order to adjust the pretty printer behavior" + , " IList _render(IList tokens, String token) => switch (token) {" + , " '' || ' ' => tokens," + , " '{' => tokens.addAll([Text(token), NewLine.nest()])," + , " '}' => tokens.removeTrailingLines" + , " .addAll([NewLine.unnest(), Text(token), NewLine()])," + , " ';' => tokens.removeTrailingSpaces.addAll([Text(token), NewLine()])," + , " ')' || ']' || '>' || ',' => tokens" + , " .removeTrailingSpaces.removeTrailingLines" + , " .addAll([Text(token), Space()])," + , " '\\$' ||" + , " '&' ||" + , " '@' ||" + , " '!' ||" + , " '#' ||" + , " '(' ||" + , " '[' ||" + , " '<' ||" + , " '.' =>" + , " tokens.removeTrailingLines.add(Text(token))," + , " _ => tokens.addAll([Text(token), Space()])" + , " };" + , "}" + , "" + , "extension TokensList on IList {" + , " IList get removeTrailingLines =>" + , " isNotEmpty && last is NewLine ? removeLast().removeTrailingLines : this;" + , " IList get removeTrailingSpaces =>" + , " isNotEmpty && last is Space ? removeLast().removeTrailingSpaces : this;" + , "}" + , "" + , "extension PrintableInt on int {" + , " String get print => toString();" + , "}" + , "" + , "extension PrintableDouble on double {" + , " String get print => toString();" + , "}" + , "" + , "extension PrintableString on String {" + , " String get print => this;" + , "}" + , "" + , "extension PrintableIList on IList {" + , " String get print => toString();" + , "}" + , "" + , "final _renderer = StringRenderer();" + , "" + , "mixin Printable {" + , " String get print => \'[not implemented]\';" + , "}" ] + + buildUserToken :: String -> [String] + buildUserToken token = + let name = censorName langName token + in [ "Iterable _prettify" ++ name ++ "(ast." ++ name +++ "x) => [x];" ] + + generateLabelPrinters :: (Cat, [Rule]) -> [String] + generateLabelPrinters (cat, rawRules) = let + rules = [ (wpThing $ funRule rule, rhsRule rule) | rule <- rawRules ] + in if isList cat + then let + sep = findSep rules + term = findTerm rules + vType = cat2DartType' $ normCat cat + precedence = precCat cat + in [ + generateListPrettifier vType precedence sep term, + generateListPrintFunction vType precedence ] + else let + funs = [ fst rule | rule <- rules ] + in mapMaybe (generateConcreteMapping cat) rules + ++ (concatMap generatePrintFunction $ map str2DartClassName' $ filter representedInAst funs) + where + representedInAst :: String -> Bool + representedInAst fun = not ( + isNilFun fun || + isOneFun fun || + isConsFun fun || + isConcatFun fun || + isCoercion fun ) + findSep :: [(String, [Either Cat String])] -> String + findSep [] = "" + findSep ((name, rhs):rest) + | isConsFun name = case [ sep | Right sep <- rhs ] of + (a:_) -> a + [] -> findSep rest + | otherwise = findSep rest + findTerm :: [(String, [Either Cat String])] -> String + findTerm [] = "" + findTerm ((name, rhs):rest) + | isOneFun name = case [ sep | Right sep <- rhs ] of + (a:_) -> a + [] -> findTerm rest + | otherwise = findTerm rest + + generateRulePrinters :: Data -> [String] + generateRulePrinters (cat, rules) = + let funs = map fst rules + fun = catToStr cat + in + if isList cat + || isNilFun fun + || isOneFun fun + || isConsFun fun + || isConcatFun fun + || isCoercion fun + || fun `elem` funs + then [] -- the category is not presented in the AST + else + let className = cat2DartClassName' cat + in (generateRuntimeMapping className $ map fst rules) ++ + (generatePrintFunction className) + + generateRuntimeMapping :: String -> [String] -> [String] + generateRuntimeMapping name ruleNames = [ + "Iterable _prettify" ++ name ++ "(ast." ++ name +++ "a) => switch (a) {" ] ++ + (indent 2 $ map mapRule $ map str2DartClassName' ruleNames) ++ + (indent 1 [ "};" ]) + where + mapRule name = "ast." ++ name +++ "a => _prettify" ++ name ++ "(a)," + + generateConcreteMapping :: Cat -> (String, [Either Cat String]) -> Maybe (String) + generateConcreteMapping cat (label, tokens) + | isNilFun label || + isOneFun label || + isConsFun label || + isConcatFun label || + isCoercion label = Nothing -- these are not represented in the AST + | otherwise = -- a standard rule + let + tokensReversed = foldl (\acc x -> x : acc) [] tokens + className = str2DartClassName' label + cats = [ cat | Left cat <- tokensReversed ] + vars = zip (map precCat cats) (getVars' cats) + in Just . unlines $ + [ "Iterable _prettify" ++ className ++ "(ast." ++ className +++ "a) => [" ] + ++ (indent 1 $ generateRuleRHS tokensReversed vars []) + ++ ["];"] + + generateListPrettifier :: DartVarType -> Integer -> String -> String -> String + generateListPrettifier vType@(n, name) prec separator terminator = + "Iterable _prettify" ++ printerListName vType prec ++ "(" ++ + printerListType vType +++ "a) => [...a.expand((e" ++ show n ++ + ") => [\'" ++ separator ++ "\'," +++ + (buildArgument (n - 1, name) prec ("e" ++ show n)) ++ + "],).skip(1)," +++ "\'" ++ terminator ++ "\',];" + + generateRuleRHS :: [Either Cat String] -> [(Integer, DartVar)] -> [String] -> [String] + generateRuleRHS [] _ lines = lines + generateRuleRHS (token:rTokens) [] lines = case token of + Right terminal -> generateRuleRHS + rTokens + [] + lines ++ (buildTerminal terminal) + Left _ -> generateRuleRHS rTokens [] lines + generateRuleRHS + (token:rTokens) + ((prec, variable@(vType, _)):rVariables) + lines = case token of + Right terminal -> generateRuleRHS + rTokens + ((prec, variable):rVariables) + lines ++ (buildTerminal terminal) + Left _ -> generateRuleRHS + rTokens + rVariables + lines ++ [ buildArgument vType prec ("a." ++ buildVariableName variable) ++ "," ] + + buildTerminal :: String -> [String] + buildTerminal = (\x -> [x]) + . ("'" ++) + . (++ "',") + . (concatMap (\c -> if c == '\\' then ['\\', '\\'] else [c])) + + buildArgument :: DartVarType -> Integer -> String -> String + buildArgument (0, name) prec argument = + if checkRegistered name + then argument ++ ".print" + else "..._prettify" ++ (str2DartClassName' name) ++ "(" ++ argument ++ ")" + buildArgument vType@(n, name) prec argument = "..._prettify" + ++ printerListName vType prec ++ "(" ++ argument ++ ")" + + generatePrintFunction :: String -> [String] + generatePrintFunction name = [ + "String print" ++ name ++ "(ast." ++ name +++ "x)" +++ "=> _renderer.print(_prettify" ++ name ++ "(x));" ] + + generateListPrintFunction :: DartVarType -> Integer -> String + generateListPrintFunction dvt prec = + "String print" ++ printerListName dvt prec ++ "(" ++ printerListType dvt +++ "x)" +++ "=> _renderer.print(_prettify" ++ printerListName dvt prec ++ "(x));" + + printerListName :: DartVarType -> Integer -> String + printerListName (0, name) prec = name ++ if prec <= 0 then "" else (show prec) + printerListName (n, name) prec = "List" ++ (printerListName (n - 1, name) prec) + + printerListType :: DartVarType -> String + printerListType (0, name) + | checkBuiltIn name = name + | otherwise = "ast." ++ name + printerListType (n, name) = "Iterable<" ++ printerListType (n - 1, name) ++ ">" \ No newline at end of file diff --git a/source/src/BNFC/Backend/Dart/CFtoDartSkeleton.hs b/source/src/BNFC/Backend/Dart/CFtoDartSkeleton.hs new file mode 100644 index 00000000..a1809bc4 --- /dev/null +++ b/source/src/BNFC/Backend/Dart/CFtoDartSkeleton.hs @@ -0,0 +1,81 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Dart.CFtoDartSkeleton (cf2DartSkeleton) where + +import Data.Maybe ( mapMaybe ) + +import BNFC.CF +import BNFC.Utils ( (+++) ) + +import BNFC.Backend.Common.NamedVariables ( UserDef ) +import BNFC.Backend.Dart.Common + + +cf2DartSkeleton :: String -> CF -> String -> String +cf2DartSkeleton langName cf importLang = + unlines $ + [ "import 'package:fast_immutable_collections/fast_immutable_collections.dart';" + , importLang + , "A identityFn(A a) => a;" ] + ++ (map buildUserToken [ n | (n,_) <- tokenPragmas cf ]) -- generate user-defined types + ++ (concatMap genData $ getAbstractSyntax cf) + where + censorName' = censorName langName + str2DartClassName' = str2DartClassName langName + getVars' = getVars langName + cat2DartClassName' = cat2DartClassName langName + cat2DartType' = cat2DartType langName + buildUserToken :: UserDef -> String + buildUserToken token = + "String interpret" ++ (censorName' token) ++ "(x) => x;" + + genData :: Data -> [String] + genData (cat, rules) + | (catToStr cat) `elem` (map fst rules) = [] -- the category is also a function + | otherwise = + let name = cat2DartClassName' cat + varType = buildVariableTypeFromDartType $ cat2DartType' cat + in [ "String interpret" ++ name ++ "(" ++ varType +++ "e) =>" ] + ++ (indent 1 $ if isList cat + then [ "\"$e\";" ] + else [ "switch (e) {" ] + ++ (indent 1 $ mapMaybe genBranch rules) + ++ [ "};" ]) + + genBranch :: (Fun, [Cat]) -> Maybe (String) + genBranch (fun, rhs) + | isNilFun fun || + isOneFun fun || + isConsFun fun = Nothing -- these are not represented in the Absyn + | otherwise = -- a standard rule + let + className = str2DartClassName' fun + varName = lowerFirst $ censorName' className + vars = getVars' rhs + in Just $ + className +++ varName +++ "=> \"" ++ className ++ "(" + ++ (concat $ (drop 1) $ arguments (genVarRepr varName) vars) + ++ ")\"," + where + arguments _ [] = [] + arguments generator (x:vars) = + [ ", ", "${" ++ (generator x) ++ "}" ] ++ (arguments generator vars) + + genVarRepr :: String -> DartVar -> String + genVarRepr varName variable@((n, varType), _) = let + varCall = varName ++ "." ++ (buildVariableName variable) + interp = interpreter varType in + if n > 0 then + varCall ++ ".map(" ++ (unpack interp (n - 1)) ++ ")" + else + interp ++ "(" ++ varCall ++ ")" + where + unpack funName n + | n <= 0 = funName + | otherwise = let varName = "e" ++ show n in + "(" ++ varName ++ ") => " ++ varName ++ ".map(" ++ (unpack funName (n - 1)) ++ ")" + interpreter varType + | varType /= (censorName' varType) = "identityFn" + | otherwise = "interpret" ++ varType diff --git a/source/src/BNFC/Backend/Dart/Common.hs b/source/src/BNFC/Backend/Dart/Common.hs new file mode 100644 index 00000000..f8181359 --- /dev/null +++ b/source/src/BNFC/Backend/Dart/Common.hs @@ -0,0 +1,254 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Dart.Common where + +import qualified Data.Map as Map +import BNFC.CF +import Data.Maybe +import qualified Data.Char as Char + + +cat2DartClassName :: String -> Cat -> String +cat2DartClassName langName cat = str2DartClassName langName $ identCat $ normCat cat + + +-- Pick a class name that is appropriate for the Dart +str2DartClassName :: String -> String -> String +str2DartClassName langName str = upperFirst $ censorName langName str + + +-- Pick a class name that is appropriate for the Antlr +str2AntlrClassName :: String -> String +str2AntlrClassName str = upperFirst str + + +cat2DartType :: String -> Cat -> DartVarType +cat2DartType langName cat = toList (0, cat) + where + toList :: (Int, Cat) -> DartVarType + toList (n, (ListCat name)) = toList (n + 1, name) + toList (n, name) = + ( n + , let n = catToStr $ normCat name + in case (name2DartBuiltIn n) of + Just bn -> bn + Nothing -> censor n ) + censor = censorName langName + + +cat2DartName :: String -> Cat -> String +cat2DartName langName cat = toList $ normCat cat + where + toList (ListCat name) = toList name ++ "List" + toList name = censorName langName $ catToStr name + + +name2DartBuiltIn :: String -> Maybe String +name2DartBuiltIn name + | name == "Integer" = Just "int" + | name == "Double" = Just "double" + | name == "Ident" = Just "String" + | name == "String" = Just "String" + | name == "Char" = Just "Character" + | otherwise = Nothing + + +upperFirst :: [Char] -> [Char] +upperFirst [] = [] +upperFirst (letter:rest) = Char.toUpper letter : rest + + +lowerFirst :: [Char] -> [Char] +lowerFirst [] = [] +lowerFirst (letter:rest) = Char.toLower letter : rest + + +indent :: Int -> [String] -> [String] +indent n lines = map addSpaces lines + where + addSpaces :: String -> String + addSpaces line = (replicate (2 * n) ' ') ++ line + + +-- The type of an instance variable. +-- Variable type, and its name +type DartVar = (DartVarType, DartVarName) + + +-- The type of a variable type in Dart. +-- The amount of nestings, and the underlying type name without precedence. +-- Example: List> is (2, Expr). +-- This helps to build the AST builder +type DartVarType = (Int, String) + + +-- The name of a variable. +-- the name generated from the type, +-- and the number making this variable unique +type DartVarName = (String, Int) + + +-- Because of the different type representing variables, a different `getVars` is used. +getVars :: String -> [Cat] -> [DartVar] +getVars langName cats = + let variables = map toUnnamedVariable cats + namesMap = foldl countNames Map.empty variables + scoreMap = Map.map addScore namesMap + (_, vars) = foldl toDartVar (scoreMap, []) variables + in vars + where + cat2DartName' = cat2DartName langName + cat2DartType' = cat2DartType langName + toUnnamedVariable cat = ((cat2DartType' cat), (cat2DartName' cat)) + countNames namesMap (_, name) = + let current = Map.findWithDefault 0 name namesMap + next = 1 + current + in Map.insert name next namesMap + addScore n = (1, n) + toDartVar (namesMap, vars) (vType, name) = + case (Map.lookup name namesMap) of + Nothing -> ( + namesMap, + vars ++ [(vType, (name, 0))]) + Just (seen, total) -> if total <= 1 + then ( + namesMap, + vars ++ [(vType, (name, 0))]) + else ( + Map.insert name (seen + 1, total) namesMap, + vars ++ [(vType, (name, seen))]) + + +-- From a DartVar build its string representation +buildVariableName :: DartVar -> String +buildVariableName (_, (name, num)) = lowerFirst appendNumber + where + appendNumber + | num <= 0 = name + | otherwise = name ++ show num + + +-- From a DartVar make a name for the AST +buildVariableType :: DartVar -> String +buildVariableType (vType, _) = buildVariableTypeFromDartType vType + +buildVariableTypeFromDartType :: DartVarType -> String +buildVariableTypeFromDartType vType = unpack vType + where + unpack (0, name) = name + unpack (n, name) = "IList<" ++ unpack (n - 1, name) ++ ">" + + +checkBuiltIn :: String -> Bool +checkBuiltIn name = + (lowerFirst name) `elem` concatMap + (map lowerFirst) + [ builtIn, keywords ] + + +checkRegistered :: String -> Bool +checkRegistered name = + (lowerFirst name) `elem` concatMap + (map lowerFirst) + [ builtIn, keywords, taken ] + + +-- Prevent some type or variable name to be called as some already used type or keyword +censorName :: String -> String -> String +censorName langName name + | checkRegistered name = langName ++ upperFirst name + | otherwise = name + +taken = [ "Character" ] + +builtIn = [ "int" + , "double" + , "num" + , "String" + , "bool" + , "List" + , "Set" + , "Map" + , "Runes" + , "Symbol" + , "null" + , "Null" + , "Object" + , "Enum" + , "Future" + , "Stream" + , "Iterable" + , "Never" + , "dynamic" + , "void" ] + +keywords = [ "abstract" + , "as" + , "assert" + , "async" + , "await" + , "base" + , "break" + , "case" + , "catch" + , "class" + , "const" + , "continue" + , "covariant" + , "default" + , "deferred" + , "do" + , "dynamic" + , "else" + , "enum" + , "export" + , "extends" + , "extension" + , "external" + , "factory" + , "false" + , "final" + , "finally" + , "for" + , "Function" + , "get" + , "hide" + , "if" + , "implements" + , "import" + , "in" + , "interface" + , "is" + , "late" + , "library" + , "mixin" + , "new" + , "null" + , "of" + , "on" + , "operator" + , "part" + , "required" + , "rethrow" + , "return" + , "sealed" + , "set" + , "show" + , "static" + , "super" + , "switch" + , "sync" + , "this" + , "throw" + , "true" + , "try" + , "type" + , "typedef" + , "var" + , "void" + , "when" + , "with" + , "while" + , "yield" ] \ No newline at end of file diff --git a/source/src/BNFC/Backend/Swift.hs b/source/src/BNFC/Backend/Swift.hs new file mode 100644 index 00000000..622a4ee5 --- /dev/null +++ b/source/src/BNFC/Backend/Swift.hs @@ -0,0 +1,154 @@ +{-# LANGUAGE RecordWildCards #-} + +module BNFC.Backend.Swift ( makeSwift ) where + +import Text.PrettyPrint ( text, vcat, render, nest ) + +import Prelude hiding ((<>)) +import System.FilePath ((), pathSeparator) +import System.Directory ( createDirectoryIfMissing ) +import Data.Char (toLower) + +import BNFC.Backend.Base (MkFiles, mkfile,liftIO) +import BNFC.CF (CF, getAbstractSyntax, firstEntry, catToStr, identCat, normCat ) +import BNFC.Options (SharedOptions (Options, inPackage, lang, optMake, dLanguage, antlrOpts, outDir), AntlrTarget (Swift)) +import BNFC.Utils (mkName, NameStyle (SnakeCase, CamelCase), replace, (+.+), (+++)) +import BNFC.Backend.Common.Makefile as MakeFile +import BNFC.Backend.Common.NamedVariables (firstUpperCase, firstLowerCase) +import BNFC.Backend.Antlr (makeAntlr, makeAntlr', DirectoryOptions (DirectoryOptions, baseDirectory, nameStyle)) +import BNFC.Backend.Swift.CFtoSwiftAST ( cf2SwiftAST ) +import BNFC.Backend.Swift.CFtoSwiftBuilder ( cf2SwiftBuilder ) +import BNFC.Backend.Swift.CFtoSwiftSkeleton ( cf2SwiftSkeleton ) +import BNFC.Backend.Swift.CFtoSwiftPrinter ( cf2SwiftPrinter ) +import BNFC.Backend.Swift.Common ( indent, buildVariableTypeFromSwiftType, cat2SwiftType, cat2SwiftClassName, mkBuildFnName ) + +makeSwift :: SharedOptions -> CF -> MkFiles () +makeSwift opts@Options{..} cf = do + let dirBase = replace '.' pathSeparator $ packageName + sourcesDir = dirBase "Sources" + targetDir = sourcesDir langNameUpperCased + directoryOptions = DirectoryOptions{baseDirectory = Just targetDir, nameStyle = Just CamelCase} + + makeAntlr' (opts {dLanguage = Swift, optMake = Nothing}) cf directoryOptions + + MakeFile.mkMakefile optMake $ makefileContent targetDir + + mkfile (targetDir "AbstractSyntaxTree.swift") makeSwiftComment astContent + mkfile (targetDir "Builder.swift") makeSwiftComment builderContent + mkfile (targetDir "Printer.swift") makeSwiftComment printerContent + mkfile (targetDir langNameUpperCased ++ ".swift") makeSwiftComment (publicApiContent langNameUpperCased) + mkfile (dirBase "Package.swift") makePackageHeader (packageFileContent langNameUpperCased) + mkfile "Skeleton.swift" makeSwiftComment skeletonContent + where + packageName = maybe id (+.+) inPackage $ mkName [] CamelCase lang + langName = firstLowerCase $ mkName [] CamelCase lang + langNameUpperCased = firstUpperCase langName + + astContent = cf2SwiftAST langNameUpperCased cf + builderContent = cf2SwiftBuilder cf opts + skeletonContent = cf2SwiftSkeleton langNameUpperCased cf + printerContent = cf2SwiftPrinter cf + + makeVars x = [MakeFile.mkVar n v | (n,v) <- x] + makeRules x = [MakeFile.mkRule tar dep recipe | (tar, dep, recipe) <- x] + + makefileVars = vcat $ makeVars + [("LANG", langNameUpperCased) + , ("LEXER_NAME", langNameUpperCased ++ "Lexer") + , ("PARSER_NAME", langNameUpperCased ++ "Parser") + -- , ("ANTLR4", "java -Xmx500M -cp \"/usr/local/lib/antlr-4.13.1-complete.jar:$CLASSPATH\" org.antlr.v4.Tool") + , ("ANTLR4", "antlr4") -- installed using pip + ] + + refVarInSrc dirBase refVar = dirBase MakeFile.refVar refVar + + rmFile :: (String -> String) -> String -> String -> String + rmFile refSrcVar refVar ext = "rm -f" +++ refSrcVar refVar ++ ext + + makefileRules refSrcVar = + let rmInSrc = rmFile refSrcVar + in vcat $ makeRules + [ (".PHONY", ["all", "clean", "remove"], []) + , ("all", [MakeFile.refVar "LANG"], []) + , ("lexer" + , [refSrcVar "LEXER_NAME" ++ ".g4"] + , [MakeFile.refVar "ANTLR4" +++ "-Dlanguage=Swift" +++ refSrcVar "LEXER_NAME" ++ ".g4"]) + , ("parser" + , [refSrcVar "PARSER_NAME" ++ ".g4"] + , [MakeFile.refVar "ANTLR4" +++ "-Dlanguage=Swift" +++ "-no-listener" +++ "-no-visitor" +++ refSrcVar "PARSER_NAME" ++ ".g4"]) + , (MakeFile.refVar "LANG", ["lexer", "parser", "clean"], []) + , ("clean", [], + [ + rmInSrc "LEXER_NAME" ".interp" + , rmInSrc "LEXER_NAME" ".tokens" + , rmInSrc "PARSER_NAME" ".interp" + , rmInSrc "PARSER_NAME" ".tokens" + , rmInSrc "LEXER_NAME" ".g4" + , rmInSrc "PARSER_NAME" ".g4" + ]) + , ("remove", [], ["rm -rf" +++ MakeFile.refVar "LANG"]) + ] + + makefileContent dirBase _ = vcat [makefileVars, "", makefileRules $ refVarInSrc dirBase, ""] + + -- Content of Package.swift, uses to declare swift package + packageFileContent langName = vcat + [ "import PackageDescription" + , "" + , "let package = Package(" + , nest 2 $ vcat + [ text $ "name: \"" ++ langName ++ "\"," + , "products: [" + , nest 2 $ vcat + [ ".library(" + , text $ " name: \"" ++ langName ++ "\"," + , text $ " targets: [\"" ++ langName ++ "\"]" + , ")" + ] + , "]," + ] + , nest 2 $ vcat + [ "dependencies: [" + , " .package(name: \"Antlr4\", url: \"https://github.com/antlr/antlr4\", from: \"4.12.0\")" + , "]," + ] + , nest 2 $ vcat + [ "targets: [" + , text $ " .target(name: \"" ++ langName ++ "\", dependencies: [\"Antlr4\"])" + , "]" + ] + , ")" + ] + + publicApiContent langName = vcat + [ "import Antlr4" + , "" + , text $ "public func ast(from text: String) -> Result<" ++ catToStr firstCat ++ ", Error> {" + , nest 2 $ vcat + [ "let input = ANTLRInputStream(text)" + , text $ "let lexer =" +++ langName ++ "Lexer(input)" + , "let tokens = CommonTokenStream(lexer)" + , "do {" + , nest 2 $ vcat + [ text $ "let parser = try" +++ langName ++ "Parser(tokens)" + , text $ "let ctx = try parser." ++ (firstLowerCase $ identCat $ normCat firstCat) ++ "()" + , text $ "let program = try" +++ mkBuildFnName firstCat ++ "(ctx)" + , "return .success(program)" + ] + , "} catch {" + , " return .failure(error)" + , "}" + ] + , "}" + ] + where + firstCat = firstEntry cf + + +makeSwiftComment :: String -> String +makeSwiftComment = ("// Swift " ++) + +makePackageHeader :: String -> String +makePackageHeader str = toolingVersion ++ "\n" ++ (makeSwiftComment str) + where + toolingVersion = "// swift-tools-version: 5.9" \ No newline at end of file diff --git a/source/src/BNFC/Backend/Swift/CFToSwiftBuilder.hs b/source/src/BNFC/Backend/Swift/CFToSwiftBuilder.hs new file mode 100644 index 00000000..82abae52 --- /dev/null +++ b/source/src/BNFC/Backend/Swift/CFToSwiftBuilder.hs @@ -0,0 +1,146 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Swift.CFtoSwiftBuilder (cf2SwiftBuilder) where + +import Data.Bifunctor (Bifunctor(second)) +import Data.List (intercalate, intersperse) +import Data.Maybe (mapMaybe) + +import Text.PrettyPrint.HughesPJClass (Doc, text, vcat) + +import BNFC.Utils ((+++), camelCase_) +import BNFC.CF +import BNFC.Backend.Swift.Common +import BNFC.Options (SharedOptions (lang)) +import BNFC.Backend.Antlr.CFtoAntlr4Parser (antlrRuleLabel, makeLeftRecRule) +import BNFC.Backend.Common.NamedVariables (firstUpperCase, firstLowerCase) + +type RuleData = (Cat, [(String, SentForm)]) + +cf2SwiftBuilder :: CF -> SharedOptions -> Doc +cf2SwiftBuilder cf opts = vcat $ intersperse (text "") + [ importDecls + , errorsDecl + , tokenDecls + , buildFnDecls + ] + where + language = lang opts + + importDecls :: Doc + importDecls = vcat + [ "import Foundation" + , "import Antlr4" + ] + + errorsDecl = buildErrors + tokenDecls = vcat $ intersperse (text "") buildTokensFuns + buildFnDecls = vcat $ intersperse (text "") buildFuns + + buildFuns = map (mkBuildFunction language) datas + buildTokensFuns = map mkBuildTokenFunction allTokenCats + + allTokenCats = getAllTokenCats cf + datas = cfToGroups cf + +buildErrors :: Doc +buildErrors = vcat + [ "enum BuildError: Error {" + , indent 2 "case UnexpectedParseContext(String)" + , "}" + ] + +mkThrowErrorStmt :: Cat -> String +mkThrowErrorStmt cat = "throw BuildError.UnexpectedParseContext(\"Error: ctx should be an instance of" +++ camelCase_ (identCat cat) ++ "Context" ++ "\")" + +-- | generates function code for building appropriate node for TokenCat. +mkBuildTokenFunction :: Cat -> Doc +mkBuildTokenFunction tokenCat = vcat + [ text $ "func" +++ fnName ++ "(_ ctx: Token) throws ->" +++ returnType +++ "{" + , indent 2 $ "return" +++ returnType ++ "(" ++ value ++ ")" + , "}" + ] + where + tokenName = catToStr tokenCat + fnName = mkBuildFnName tokenCat + returnType = catToSwiftType tokenCat + value = case tokenName of + "Integer" -> "Int(ctx.getText()!)!" + "Double" -> "Double(ctx.getText()!)!" + _ -> "ctx.getText()!" + +mkBuildFunction :: String -> RuleData -> Doc +mkBuildFunction lang (cat, rulesWithLabels) = vcat + [ text $ "func" +++ mkBuildFnName cat ++ "(_ ctx: " ++ (addParserPrefix lang $ identCat cat) ++ "Context) throws ->" +++ catToSwiftType cat +++ "{" + , indent 2 "switch ctx {" + , vcat $ map mkCaseStmt datas + , indent 4 "default:" + , indent 6 $ mkThrowErrorStmt cat + , indent 2 "}" + , "}" + ] + where + datas = zip rulesWithLabels [1..] + + mkCaseStmt :: ((String, SentForm), Integer) -> Doc + mkCaseStmt ((ruleLabel, rhsRule), ifIdx) = vcat + [ indent 4 $ "case let ctx as" +++ addParserPrefix lang (antlrRuleLabel cat ruleLabel antlrRuleLabelIdx) ++ "Context:" + , vcat $ map text $ mCaseBody ruleLabel + ] + + where + antlrRuleLabelIdx = if isCoercion ruleLabel then Just ifIdx else Nothing + rhsRuleWithIdx = mapMaybe (\(rule, idx) -> either (\cat -> Just (cat, idx)) (\_ -> Nothing) rule) $ zip rhsRule [1..] + mkPattern idx = "p_" ++ show ifIdx ++ "_" ++ show idx + -- mkPattern idx = "expr(" ++ show idx ++ ")!" + + mCaseBody ruleLabel + | isCoercion ruleLabel = map (\(cat, idx) -> indentStr 6 $ "return try" +++ mkBuildFnName cat ++ "(ctx." ++ mkPattern idx ++ ")") rhsRuleWithIdx + | isNilFun ruleLabel = emptyListBody + | isOneFun ruleLabel = oneListBody + | isConsFun ruleLabel = consListBody + | otherwise = + concat + [ zipWith + (\ (cat, idx) varName + -> indentStr 6 + $ "let" +++ wrapIfNeeded varName + +++ "= try" +++ mkBuildFnName cat ++ "(ctx." ++ mkPattern idx ++ ")") + rhsRuleWithIdx varNames + , [ indentStr 6 returnStatement] + ] + where + varNames = getVarsFromCats rhsCats + rhsCats = map fst rhsRuleWithIdx + returnStatementBase = "return" +++ "." ++ (firstLowerCase ruleLabel) + returnStatement + | null varNames = returnStatementBase + | otherwise = returnStatementBase ++ "(" ++ intercalate ", " (map wrapIfNeeded varNames) ++ ")" + + emptyListBody = [indentStr 6 "return []"] + oneListBody = map (\(cat, idx) -> indentStr 6 $ "let data = try" +++ mkBuildFnName cat ++ "(ctx." ++ mkPattern idx ++ ")") rhsRuleWithIdx ++ [ indentStr 4 "return [data]"] + consListBody = + [ indentStr 6 $ "let value1 = try" +++ mkBuildFnName firstCat ++ "(ctx." ++ mkPattern firstIdx ++ ")" + , indentStr 6 $ "let value2 = try" +++ mkBuildFnName secondCat ++ "(ctx." ++ mkPattern secondIdx ++ ")" + , indentStr 6 $ "return" +++ resultList + ] + where + (firstCat, firstIdx) = head rhsRuleWithIdx + (secondCat, secondIdx) = rhsRuleWithIdx !! 1 + (itemVar, listVar) = if isList firstCat then ("value2", "value1") else ("value1", "value2") + resultList = if isList firstCat + then + listVar +++ "+" +++ "[" ++ itemVar ++ "]" + else + "[" ++ itemVar ++ "]" +++ "+" +++ listVar + +cfToGroups :: CF -> [RuleData] +cfToGroups cf = map (second (map (ruleToData . makeLeftRecRule cf))) $ ruleGroups cf + where + ruleToData rule = ((wpThing . funRule) rule, rhsRule rule) + + +addParserPrefix :: String -> String -> String +addParserPrefix lang name = (firstUpperCase lang) ++ "Parser." ++ name \ No newline at end of file diff --git a/source/src/BNFC/Backend/Swift/CFtoSwiftAST.hs b/source/src/BNFC/Backend/Swift/CFtoSwiftAST.hs new file mode 100644 index 00000000..b90cfd95 --- /dev/null +++ b/source/src/BNFC/Backend/Swift/CFtoSwiftAST.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} + +module BNFC.Backend.Swift.CFtoSwiftAST (cf2SwiftAST) where + +import Data.Maybe (mapMaybe) +import Data.List (intercalate, intersperse) +import Text.PrettyPrint.HughesPJClass (Doc, text, vcat, nest, ($$)) + +import BNFC.CF +import BNFC.Utils ((+++)) +import BNFC.Backend.Swift.Common +import BNFC.Backend.Common.NamedVariables (UserDef) + +-- | Produces abstract data types in Swift +cf2SwiftAST :: String -> CF -> Doc +cf2SwiftAST langName cf = vcat + [ imports + , empty + , vcat (intersperse empty (map mkTokenDecl allTokenNames)) + , empty + , vcat (intersperse empty (concatMap prData rules)) + ] + where + empty = text "" + rules = getAbstractSyntax cf + imports = vcat [text "import Foundation"] + allTokenNames = literals cf + + -- | Generates a Swift struct for a token. + mkTokenDecl :: String -> Doc + mkTokenDecl tokenName = vcat + [ text $ "public struct" +++ catToSwiftType (TokenCat tokenName) +++ "{" + , nest 2 $ text $ "public let value: " ++ value + , empty + , nest 2 $ text $ "public init(_ value:" +++ value ++ ") {" + , nest 4 $ text "self.value = value" + , nest 2 $ text "}" + , text "}" + ] + where + value + | tokenName == catInteger = "Int" + | tokenName == catDouble = "Double" + | otherwise = "String" + + -- | Generates enums and cases for a given data type. + prData :: Data -> [Doc] + prData (cat, rules) = categoryClass + where + funs = map fst rules + cases = mapMaybe (prRule cat) rules + categoryClass + | catToStr cat `elem` funs || isList cat = [] + | otherwise = + let name = catToSwiftType cat + in [ vcat $ text ("public indirect enum" +++ wrapIfNeeded name +++ "{") + : nest 2 (vcat cases) + : [text "}"] + ] + + -- | Generates individual cases for enum definitions. + prRule :: Cat -> (Fun, [Cat]) -> Maybe Doc + prRule cat (fun, cats) + | isNilFun fun || isOneFun fun || isConsFun fun = Nothing + | otherwise = Just $ text $ "case" +++ caseName ++ resultAssociatedValuesConcatenated + where + caseName = str2SwiftClassName langName fun + caseAssociatedValues = map (wrapIfNeeded . catToSwiftType) cats + resultAssociatedValuesConcatenated + | null cats = "" + | otherwise = "(" ++ intercalate ", " caseAssociatedValues ++ ")" diff --git a/source/src/BNFC/Backend/Swift/CFtoSwiftPrinter.hs b/source/src/BNFC/Backend/Swift/CFtoSwiftPrinter.hs new file mode 100644 index 00000000..c16e2fc9 --- /dev/null +++ b/source/src/BNFC/Backend/Swift/CFtoSwiftPrinter.hs @@ -0,0 +1,386 @@ +module BNFC.Backend.Swift.CFtoSwiftPrinter (cf2SwiftPrinter) where + +import Data.Either (lefts, rights) +import Data.List (nub, intercalate, find, uncons, intersperse) + +import Text.PrettyPrint.HughesPJClass (Doc, text, vcat, hcat, nest) + +import BNFC.CF (CF, ruleGroups, Rul (rhsRule, funRule), Cat (Cat, ListCat, TokenCat, CoercCat), WithPosition (wpThing), IsFun (isCoercion, isConsFun, isOneFun, isNilFun), catToStr, SentForm, rulesForNormalizedCat, normCat, normCatOfList, catOfList, isList, allParserCats, rulesForCat) +import BNFC.Utils ((+++)) +import BNFC.Backend.Swift.Common (catToSwiftType, indent, wrapSQ, getVarsFromCats, getAbsynWithoutLists, getAllTokenTypenames, getAllTokenCats, mkTypeName, wrapIfNeeded) +import BNFC.Backend.Common.NamedVariables (firstUpperCase, firstLowerCase) +import Data.Maybe (isJust, isNothing, fromMaybe) + +prettyPrintPropertyName :: String +prettyPrintPropertyName = "printed" + +prettyPrintRenderClassName :: String +prettyPrintRenderClassName = "Rendered" + +prettyPrintRenderCall :: String +prettyPrintRenderCall = prettyPrintRenderClassName ++ ".shared.render" + +-- | generate pretty-printers for nodes of an AST +cf2SwiftPrinter :: CF -> Doc +cf2SwiftPrinter cf = vcat + [ rendererDeclaration + , tokenPrinterDecl + , nodesPrintersDecls + , "" + , nodesPrettifiersDecls + ] + where + tokenPrinterDecl = mkTokenPrinter cf + + -- we intentionally want to have rules for list cats, which have items of type Coerc + cats = let isCoercCat (CoercCat _ _) = True + isCoercCat _ = False + in filter (not . isCoercCat) $ allParserCats cf + + nodesPrettifiersDecls = vcat $ intersperse (text "") $ map (mkNodePrettifier cf) cats + nodesPrintersDecls = vcat $ intersperse (text "") $ (map mkNodePrinter cats) + + rules = map (wpThing . funRule) $ + concatMap + (filter (not . isCoercion) . rulesForNormalizedCat cf) + (filter (not . isList) cats) + +rendererDeclaration :: Doc +rendererDeclaration = vcat + [ text $ "final class" +++ className +++ "{" + , nest 2 $ tokenEnumDeclaration + , nest 2 $ text $ "static let shared =" +++ className ++ "()" + , nest 2 $ text $ "private let indentSize = 2" + , text "" + , nest 2 "private init() {}" + , text "" + , nest 2 renderFunctionDeclaration + , nest 2 transformFunctionDeclaration + , nest 2 groupTokensFunctionDeclaration + , nest 2 addIndentationFunctionDeclaration + , nest 2 dropTrailingSpacesFunctionDeclaration + , nest 2 dropTrailingNewlinesFunctionDeclaration + , "}" + , "" + ] + where + className = prettyPrintRenderClassName + tokenEnumDeclaration = vcat + [ "private enum Token {" + , nest 2 body + , "}" + , "" + ] + where + body = vcat + [ "case text(value: String)" + , "case newline(indentShift: Int)" + , "case space" + , "" + , "func toString() -> String {" + , indent 2 "switch self {" + , indent 4 "case .text(let value):" + , indent 6 "return value" + , indent 4 "case .newline:" + , indent 6 "return \"\\n\"" + , indent 4 "case .space:" + , indent 6 "return \" \"" + , indent 2 "}" + , "}" + ] + + renderFunctionDeclaration = vcat + [ "func render(_ tokens: [String]) -> String {" + , nest 2 body + , "}" + , "" + ] + where + body = vcat + [ "let transformedTokens = transform(tokens)" + , "let groupedTokens = groupTokens(transformedTokens)" + , "return groupedTokens" + , indent 2 ".map { addIndentation(to: $0) }" + , indent 2 ".map { $0.map { $0.toString() }.joined() }" + , indent 2 ".joined(separator: \"\\n\")" + ] + + transformFunctionDeclaration = vcat + [ "private func transform(_ tokens: [String]) -> [Token] {" + , nest 2 body + , "}" + , "" + ] + where + body = vcat + [ "var result: [Token] = []" + , "var insideBrackets = false" + , "for token in tokens {" + , indent 2 "switch token {" + , nest 4 casesDeclaration + , indent 2 "}" + , "}" + , "dropTrailingSpaces(from: &result)" + , "dropTrailingNewlines(from: &result)" + , "return result" + ] + casesDeclaration = vcat + [ "case \"\", \" \":" + , indent 2 "if insideBrackets {" + , indent 4 "continue" + , indent 2 "}" + , "case \"{\":" + , indent 2 "result.append(.text(value: token))" + , indent 2 "result.append(.newline(indentShift: 1))" + , "case \"}\":" + , indent 2 "dropTrailingNewlines(from: &result)" + , indent 2 "result.append(.newline(indentShift: -1))" + , indent 2 "result.append(.text(value: token))" + , indent 2 "result.append(.newline(indentShift: 0))" + , "case \"[\":" + , indent 2 "dropTrailingSpaces(from: &result)" + , indent 2 "result.append(.space)" + , indent 2 "result.append(.text(value: token))" + , indent 2 "insideBrackets = true" + , "case \"]\":" + , indent 2 "dropTrailingSpaces(from: &result)" + , indent 2 "result.append(.text(value: token))" + , indent 2 "insideBrackets = false" + , indent 2 "result.append(.space)" + , "case \"(\", \")\", \"<\", \">\", \",\", \".\":" + , indent 2 "dropTrailingSpaces(from: &result)" + , indent 2 "if token == \")\" || token == \"]\" || token == \"}\" {" + , indent 4 "dropTrailingNewlines(from: &result)" + , indent 2 "}" + , indent 2 "result.append(.text(value: token))" + , indent 2 "if token != \".\" && token != \"(\" && !insideBrackets {" + , indent 4 "result.append(.space)" + , indent 2 "}" + , "case \";\":" + , indent 2 "dropTrailingSpaces(from: &result)" + , indent 2 "dropTrailingNewlines(from: &result)" + , indent 2 "result.append(.text(value: token))" + , indent 2 "result.append(.newline(indentShift: 0))" + , "case \"return\":" + , indent 2 "result.append(.text(value: token))" + , indent 2 "result.append(.space)" + , "default:" + , indent 2 "result.append(.text(value: token))" + , indent 2 "if !insideBrackets {" + , indent 4 "result.append(.space)" + , indent 2 "}" + ] + + + groupTokensFunctionDeclaration = vcat + [ "private func groupTokens(_ tokens: [Token]) -> [(indentationLevel: Int, tokens: [Token])] {" + , indent 2 "var groups: [(indentationLevel: Int, tokens: [Token])] = []" + , indent 2 "var currentIndentation = 0" + , indent 2 "for token in tokens {" + , indent 4 "if case .newline(let shift) = token {" + , indent 6 "currentIndentation += shift" + , indent 6 "groups.append((currentIndentation, []))" + , indent 4 "} else {" + , indent 6 "if groups.isEmpty {" + , indent 8 "groups.append((currentIndentation, []))" + , indent 6 "}" + , indent 6 "groups[groups.count - 1].tokens.append(token)" + , indent 4 "}" + , indent 2 "}" + , indent 2 "return groups" + , "}" + , "" + ] + + addIndentationFunctionDeclaration = vcat + [ "private func addIndentation(to group: (indentationLevel: Int, tokens: [Token])) -> [Token] {" + , indent 2 "var tokens = group.tokens" + , indent 2 "if group.indentationLevel > 0 {" + , indent 4 "tokens.insert(.text(value: String(repeating: \" \", count: indentSize * group.indentationLevel)), at: 0)" + , indent 2 "}" + , indent 2 "return tokens" + , "}" + , "" + ] + + dropTrailingSpacesFunctionDeclaration = vcat + [ "private func dropTrailingSpaces(from tokens: inout [Token]) {" + , indent 2 "while let last = tokens.last, case .space = last {" + , indent 4 "tokens.removeLast()" + , indent 2 "}" + , "}" + , "" + ] + + dropTrailingNewlinesFunctionDeclaration = vcat + [ "private func dropTrailingNewlines(from tokens: inout [Token]) {" + , indent 2 "while let last = tokens.last, case .newline = last {" + , indent 4 "tokens.removeLast()" + , indent 2 "}" + , "}" + ] + +-- | generate function which will print user-defined and predefined tokens. +mkTokenPrinter :: CF -> Doc +mkTokenPrinter cf = vcat + [ tokenPrinters ] + where + -- allTokenTypes = getAllTokenTypenames cf + -- tokensUnionType = intercalate " | " allTokenTypes + + tokenPrinters = vcat $ map mkTokenPrinter (getAllTokenCats cf) + mkTokenPrinter tokenCat = vcat + [ text $ "extension" +++ catToSwiftType tokenCat +++ "{" + , nest 2 $ text $ "public var" +++ prettyPrintPropertyName ++ ": String {" + , indent 4 "String(value)" + , indent 2 "}" + , "}" + , "" + ] + +mkNodePrinter :: Cat -> Doc +mkNodePrinter cat@(Cat _) = vcat + [ text $ "extension" +++ catToSwiftType cat +++ "{" + , nest 2 $ text $ "public var" +++ printFnName ++ ": String {" + , indent 4 $ prettyPrintRenderCall ++ "(" ++ prettifyFnName ++ "(self))" + , indent 2 "}" + , "}" + ] + where + printFnName = mkPrintFnName cat + prettifyFnName = mkPrettifyFnName cat + +mkNodePrinter listCat@(ListCat _) = vcat + [ text $ "extension" +++ catToSwiftType listCat +++ "{" + , nest 2 $ text $ "public var" +++ printFnName ++ ": String {" + , indent 4 $ prettyPrintRenderCall ++ "(" ++ prettifyFnName ++ "(self))" + , indent 2 "}" + , "}" + ] + where + prettifyFnName = mkPrettifyFnName listCat + printFnName = mkPrintFnName listCat + catOfListType = catToSwiftType (normCatOfList listCat) + +mkNodePrinter otherCat = error $ "Unknown category for making node printer" +++ catToStr otherCat + +mkRulePrinter :: String -> Doc +mkRulePrinter ruleLabel = vcat + [ text $ "func" +++ printFnName ++ "(node:" +++ mkTypeName ruleLabel ++ ") -> String {" + , indent 2 $ "return" +++ prettyPrintRenderCall ++ "(" ++ prettifyFnName ++ "(node))" + , "}" + ] + where + printFnName = "print" ++ firstUpperCase ruleLabel + prettifyFnName = "prettify" ++ firstUpperCase ruleLabel + +mkNodePrettifier :: CF -> Cat -> Doc +mkNodePrettifier cf cat@(Cat _) = vcat $ concat + [ [text $ "func" +++ prettifyFnName ++ "(_ node:" +++ catToSwiftType cat ++ ") -> [String] {" ] + , [indent 2 $ "switch node {"] + , prettifyRulesCondition + , [indent 2 "}"] + , ["}"] + -- , rulesPrettifiers + ] + where + rules = map (\rule -> (wpThing (funRule rule), rhsRule rule)) $ + filter (not . isCoercion . funRule) $ + rulesForNormalizedCat cf cat + + mkCaseStmt rule@(ruleLabel, sentForm) = vcat + [ indent 4 $ caseDeclaration ++ (firstLowerCase ruleLabel) ++ (associatedValues varNames) ++ ":" + , indent 6 "var result = [String]()" + , nest 6 $ mkRulePrettifier rule + , indent 6 $ "return result" + ] + where + varNames = map wrapIfNeeded $ getVarsFromCats (lefts sentForm) + + caseDeclaration + | null varNames = "case ." + | otherwise = "case let ." + + associatedValues varNames + | null varNames = "" + | otherwise = "(" ++ (intercalate ", " varNames) ++ ")" + + + prettifyRulesCondition = map mkCaseStmt rules + prettifyFnName = mkPrettifyFnName cat + -- rulesPrettifiers = map mkRulePrettifier rules + -- add getVarsFromCats + +mkNodePrettifier cf listCat@(ListCat _) = vcat + [ text $ "func " ++ prettifyFnName ++ "(_ list: [" ++ catOfListType ++ "]) -> [String] {" + , nest 2 returnStmt + , "}" + ] + where + prettifyFnName = mkPrettifyFnName listCat + catOfListType = catToSwiftType (normCatOfList listCat) + + rules = rulesForCat cf listCat + consRule = find (isConsFun . funRule) rules + consSeparator = maybe Nothing findSeparator consRule + + oneRule = find (isOneFun . funRule) rules + oneSeparator = maybe Nothing findSeparator oneRule + + nilRule = find (isNilFun . funRule) rules + + findSeparator :: Rul a -> Maybe String + findSeparator rule = fmap fst (uncons terminals) + where + terminals = rights (rhsRule rule) + + separator = fromMaybe "" consSeparator + isTerminator = (isJust nilRule && isNothing oneRule && isJust consRule && isJust consSeparator) + || (isNothing nilRule && isJust oneRule && isJust oneSeparator && isJust consRule && isJust consSeparator) + + itemCat = catOfList listCat + printItemCall tokenCat@(TokenCat _) = "[item.printed]" + printItemCall cat = mkPrettifyFnName cat ++ "(item)" + + returnStmt = text listTokens + where + listMapping = "list.flatMap { item in " ++ printItemCall itemCat ++ " + " ++ "[" ++ wrapSQ separator ++ "] }" + -- listTokens = listMapping -- it depends on target language, but we do not have this knowledge in advance + listTokens = listMapping ++ if isTerminator then "" else ".dropLast()" + +mkNodePrettifier _ otherCat = error $ "Unknown category for making node prettifier" +++ catToStr otherCat + +mkRulePrettifier :: (String, SentForm) -> Doc +mkRulePrettifier (ruleLabel, sentForm) = vcat $ map text prettifyBody + where + varNames = map wrapIfNeeded $ getVarsFromCats (lefts sentForm) + + addVarNames :: [Either Cat String] -> [String] -> [Either (Cat, String) String] + addVarNames [] _ = [] + addVarNames list [] = map (either (\cat -> Left (cat, "")) Right) list + addVarNames (x:xs) allVars@(var:vars) = case x of + (Right terminal) -> Right terminal : addVarNames xs allVars + (Left cat) -> Left (cat, var) : addVarNames xs vars + + sentFormWithVarNames = addVarNames sentForm varNames + prettifiedRule = map (("result +=" +++) . (either getPrettifierForCat (\x -> "[" ++ (wrapSQ x) ++ "]"))) sentFormWithVarNames + where + getPrettifierForCat :: (Cat, String) -> String + getPrettifierForCat (tokenCat@(TokenCat _), varName) = "[" ++ varName ++ "." ++ prettyPrintPropertyName ++ "]" + getPrettifierForCat (cat, varName) = mkPrettifyFnName cat ++ "(" ++ varName ++ ")" + + prettifyBody + | null sentFormWithVarNames = [""] + | otherwise = prettifiedRule + +mkPrettifyFnName :: Cat -> String +mkPrettifyFnName cat = "prettify" ++ mkName cat + where + mkName (ListCat cat) = ("ListOf"++) $ firstUpperCase (catToStr cat) + mkName otherCat = firstUpperCase $ catToStr (normCat otherCat) + +mkPrintFnName :: Cat -> String +mkPrintFnName cat = "print" ++ mkName cat + where + mkName (ListCat itemCat) = "ListOf" ++ firstUpperCase (catToStr itemCat) + mkName otherCat = firstUpperCase $ catToStr (normCat otherCat) \ No newline at end of file diff --git a/source/src/BNFC/Backend/Swift/CFtoSwiftSkeleton.hs b/source/src/BNFC/Backend/Swift/CFtoSwiftSkeleton.hs new file mode 100644 index 00000000..af2f8726 --- /dev/null +++ b/source/src/BNFC/Backend/Swift/CFtoSwiftSkeleton.hs @@ -0,0 +1,91 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Swift.CFtoSwiftSkeleton (cf2SwiftSkeleton) where + +import Data.Maybe ( mapMaybe ) + +import BNFC.CF +import BNFC.Utils ( (+++) ) + +import BNFC.Backend.Common.NamedVariables ( UserDef, firstUpperCase ) +import BNFC.Backend.Swift.Common +import Data.List (intercalate) + + +cf2SwiftSkeleton :: String -> CF -> String +cf2SwiftSkeleton langName cf = + unlines $ + [ "import Foundation" + , "import" +++ langName + , "" + , "func identityFn(_ a: A) -> A { a }" + , "" ] + ++ (map buildUserToken [ n | (n,_) <- tokenPragmas cf ]) + ++ (concatMap genData $ getAbstractSyntax cf) + where + censorName' = censorName langName + str2SwiftClassName' = str2SwiftClassName langName + getVars' = getVars_ langName + cat2SwiftType' = cat2SwiftType langName + buildUserToken :: UserDef -> String + buildUserToken token = + "func interpret" ++ (censorName' token) ++ "(_ x: " ++ (censorName' token) ++ "Token) -> String { x.value }" + + genData :: Data -> [String] + genData (cat, rules) + | (catToStr cat) `elem` (map fst rules) = [] + | otherwise = + let name = identCat $ normCat cat + varType = buildVariableTypeFromSwiftType $ cat2SwiftType' cat + in [ "func interpret" ++ (firstUpperCase name) ++ "(_ e:" +++ varType ++ ") -> String {" ] + ++ (indent_ 1 $ if isList cat + then [ "\"\\(e)\"" ] + else [ "switch (e) {" ] + ++ (indent_ 1 $ mapMaybe genBranch rules) + ++ [ "}" ]) + ++ ["}"] + ++ [""] + + genBranch :: (Fun, [Cat]) -> Maybe (String) + genBranch (fun, rhs) + | isNilFun fun || + isOneFun fun || + isConsFun fun = Nothing + | otherwise = + let + className = str2SwiftClassName' fun + varName = lowerFirst $ censorName' className + vars = getVars' rhs + in Just $ + caseDecl className vars ++ " \"" ++ className ++ "(" + ++ (concat $ (drop 1) $ arguments (genVarRepr varName) vars) + ++ ")\"" + where + associatedValues [] = [] + associatedValues (x: vars) = [wrapIfNeeded $ buildVariableName x] ++ (associatedValues vars) + + caseDecl className [] = "case ." ++ className ++ ":" + caseDecl className vars = "case let ." ++ className ++ "(" ++ (intercalate ", " (associatedValues vars)) ++ "):" + + arguments _ [] = [] + arguments generator (x:vars) = + [ ", ", "\\(" ++ (generator x) ++ ")" ] ++ (arguments generator vars) + + genVarRepr :: String -> SwiftVar -> String + genVarRepr varName variable@((n, varType), _) = let + varCall = buildVariableName variable + interp = interpreter varType in + if n > 0 then + varCall ++ ".map(" ++ (unpack interp (n - 1)) ++ ")" -- TODO: check this + else + interp ++ "(" ++ wrapIfNeeded varCall ++ ")" + where + unpack funName n + | n <= 0 = funName + | otherwise = let varName = "e" ++ show n in + "{ " ++ varName ++ " in " ++ varName ++ ".map { " ++ (unpack funName (n - 1)) ++ " } }" -- TODO: check this + interpreter varType + | varType /= (censorName' varType) = "identityFn" + | otherwise = "interpret" ++ varType diff --git a/source/src/BNFC/Backend/Swift/Common.hs b/source/src/BNFC/Backend/Swift/Common.hs new file mode 100644 index 00000000..1476d723 --- /dev/null +++ b/source/src/BNFC/Backend/Swift/Common.hs @@ -0,0 +1,421 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TupleSections #-} + +module BNFC.Backend.Swift.Common where + +import Text.PrettyPrint (Doc, text) +import qualified Data.Map as Map +import BNFC.CF +import qualified Data.Char as Char +import Data.Char (toLower) +import BNFC.Utils (mkName, NameStyle (OrigCase, MixedCase), mkNames) +import BNFC.Backend.Common.NamedVariables (getVars, firstUpperCase, firstLowerCase) + + +cat2SwiftClassName :: String -> Cat -> String +cat2SwiftClassName langName cat = str2SwiftClassName langName $ identCat $ normCat cat + +-- Pick a class name that is appropriate for the Swift +str2SwiftClassName :: String -> String -> String +-- str2SwiftClassName langName str = upperFirst $ censorName langName str +str2SwiftClassName langName str = wrapIfNeeded $ firstLowerCase str + +-- Pick a case name that is appropriate for the Swift +str2SwiftCaseName :: String -> String -> String +str2SwiftCaseName langName str = firstLowerCase $ censorName langName str + +-- Pick a class name that is appropriate for the Antlr +str2AntlrClassName :: String -> String +str2AntlrClassName str = upperFirst str + + +cat2SwiftType :: String -> Cat -> SwiftVarType +cat2SwiftType langName cat = toList (0, cat) + where + toList :: (Int, Cat) -> SwiftVarType + toList (n, (ListCat name)) = toList (n + 1, name) + toList (n, name) = + ( n + , let n = catToStr $ normCat name + in case (name2SwiftBuiltIn n) of + Just bn -> bn + Nothing -> censor n ) + censor = censorName langName + + +cat2SwiftName :: String -> Cat -> String +cat2SwiftName langName cat = toList $ normCat cat + where + toList (ListCat name) = toList name ++ "List" + toList name = censorName langName $ catToStr name + + +name2SwiftBuiltIn :: String -> Maybe String +name2SwiftBuiltIn name + | name == "Integer" = Just "Int" + | name == "Double" = Just "Double" + | name == "Ident" = Just "String" + | name == "String" = Just "String" + | name == "Char" = Just "Character" + | otherwise = Nothing + + +upperFirst :: [Char] -> [Char] +upperFirst [] = [] +upperFirst (letter:rest) = Char.toUpper letter : rest + + +lowerFirst :: [Char] -> [Char] +lowerFirst [] = [] +lowerFirst (letter:rest) = Char.toLower letter : rest + + +indent_ :: Int -> [String] -> [String] +indent_ n lines = map addSpaces lines + where + addSpaces :: String -> String + addSpaces line = (replicate (2 * n) ' ') ++ line + + +indentString :: Int -> String -> String +indentString n line = addSpaces line + where + addSpaces :: String -> String + addSpaces line = (replicate (2 * n) ' ') ++ line + + +-- The type of an instance variable. +-- Variable type, and its name +type SwiftVar = (SwiftVarType, SwiftVarName) + + +-- The type of a variable type in Swift. +-- The amount of nestings, and the underlying type name without precedence. +-- Example: List> is (2, Expr). +-- This helps to build the AST builder +type SwiftVarType = (Int, String) + + +-- The name of a variable. +-- the name generated from the type, +-- and the number making this variable unique +type SwiftVarName = (String, Int) + + +-- Because of the different type representing variables, a different `getVars` is used. +getVars_ :: String -> [Cat] -> [SwiftVar] +getVars_ langName cats = + let variables = map toUnnamedVariable cats + namesMap = foldl countNames Map.empty variables + scoreMap = Map.map addScore namesMap + (_, vars) = foldl toSwiftVar (scoreMap, []) variables + in vars + where + cat2SwiftName' = cat2SwiftName langName + cat2SwiftType' = cat2SwiftType langName + toUnnamedVariable cat = ((cat2SwiftType' cat), (cat2SwiftName' cat)) + countNames namesMap (_, name) = + let current = Map.findWithDefault 0 name namesMap + next = 1 + current + in Map.insert name next namesMap + addScore n = (1, n) + toSwiftVar (namesMap, vars) (vType, name) = + case (Map.lookup name namesMap) of + Nothing -> ( + namesMap, + vars ++ [(vType, (name, 0))]) + Just (seen, total) -> if total <= 1 + then ( + namesMap, + vars ++ [(vType, (name, 0))]) + else ( + Map.insert name (seen + 1, total) namesMap, + vars ++ [(vType, (name, seen))]) + + +-- From a SwiftVar build its string representation +buildVariableName :: SwiftVar -> String +buildVariableName (_, (name, num)) = lowerFirst appendNumber + where + appendNumber + | num <= 0 = name + | otherwise = name ++ show num + + +-- From a SwiftVar make a name for the AST +buildVariableType :: SwiftVar -> String +buildVariableType (vType, _) = buildVariableTypeFromSwiftType vType + +buildVariableTypeFromSwiftType :: SwiftVarType -> String +buildVariableTypeFromSwiftType vType = unpack vType + where + unpack (0, name) = name + unpack (n, name) = "[" ++ unpack (n - 1, name) ++ "]" + + +checkBuiltIn :: String -> Bool +checkBuiltIn name = + (lowerFirst name) `elem` concatMap + (map lowerFirst) + [ builtIn, keywords ] + + +checkRegistered :: String -> Bool +checkRegistered name = + name `elem` (builtIn ++ keywords) + + +-- Prevent some type or variable name to be called as some already used type or keyword +censorName :: String -> String -> String +censorName langName name + | checkRegistered name = langName ++ upperFirst name + | otherwise = name + +wrapIfNeeded :: String -> String +wrapIfNeeded name + | checkRegistered name = "`" ++ name ++ "`" + | otherwise = name + +taken :: [String] +taken = [] + +builtIn :: [String] +builtIn = [ "Int" + , "Int8" + , "Int16" + , "Int32" + , "Int64" + , "UInt" + , "UInt8" + , "UInt16" + , "UInt32" + , "UInt64" + , "Double" + , "Float" + , "Float80" + , "String" + , "Character" + , "Bool" + , "Array" + , "Set" + , "Void" + , "Dictionary" + , "Optional" + , "Any" + , "Never" + , "Result" + , "Error" + , "AnyObject" + , "AnyClass" + , "ClosedRange" + , "Range" + , "Strideable" + , "RawRepresentable" + , "Hashable" + , "Codable" + , "Encodable" + , "Decodable" + , "Equatable" + , "Comparable" + , "Identifiable" + , "CaseIterable" + , "RandomNumberGenerator" + , "Sequence" + , "Collection" + , "IteratorProtocol" + ] + +keywords :: [String] +keywords = [ "some" + , "any" + , "as" + , "assert" + , "async" + , "await" + , "break" + , "case" + , "catch" + , "class" + , "struct" + , "actor" + , "var" + , "let" + , "continue" + , "default" + , "defer" + , "do" + , "dynamic" + , "else" + , "enum" + , "extension" + , "false" + , "final" + , "for" + , "get" + , "set" + , "if" + , "where" + , "import" + , "in" + , "protocol" + , "is" + , "nil" + , "rethrows" + , "return" + , "static" + , "super" + , "switch" + , "self" + , "Self" + , "super" + , "throw" + , "throws" + , "true" + , "try" + , "type" + , "typealias" + , "when" + , "with" + , "while" + , "yield" + , "fallthrough" + , "guard" + , "repeat" + , "associativity" + , "convenience" + , "didSet" + , "willSet" + , "indirect" + , "infix" + , "lazy" + , "left" + , "mutating" + , "none" + , "nonmutating" + , "optional" + , "override" + , "postfix" + , "precedence" + , "prefix" + , "Protocol" + , "required" + , "right" + , "Type" + , "unowned" + , "weak" + , "associatedtype" + , "inout" + , "func" + , "init" + , "deinit" + , "open" + , "public" + , "package" + , "internal" + , "fileprivate" + , "private" + , "private(set)" + , "subscript" + , "package" + , "macro" + , "#available" + , "#else" + , "#elseif" + , "#endif" + , "#if" + , "#file" + , "#function" + , "#line" + , "#column" + , "#fileID" + , "#filePath" + , "#selector" + , "#keyPath" + , "@discardableResult" + , "escaping" + , "noescape" + , "borrowing" + , "consuming" + , "each" + , "#main" + ] + +-- from TS implementation + +reservedKeywords :: [String] +reservedKeywords = builtIn ++ keywords + +toMixedCase :: String -> String +-- toMixedCase = firstLowerCase . mkName reservedKeywords MixedCase +toMixedCase = firstUpperCase . mkName reservedKeywords MixedCase + +-- | wrap string into single quotes. +wrapSQ :: String -> String +wrapSQ str = "\"" ++ str ++ "\"" + +-- | indent string with N spaces. +indentStr :: Int -> String -> String +indentStr size = (replicate size ' ' ++) + +mkTokenNodeName :: String -> String +mkTokenNodeName tokenName = tokenName ++ "Token" + +-- | get variable names which will be used in node structure +-- for categories used in production rule. +getVarsFromCats :: [Cat] -> [String] +getVarsFromCats cats = mkNames ["type"] OrigCase normalizedVars + where + normalizedCats = map normCat cats + indexedVars = getVars normalizedCats + + normalizeVar :: (String, Int) -> String + normalizeVar (varName, idx) = map toLower varName ++ varNameSuffix + where + varNameSuffix = if idx == 0 then "" else show idx + + normalizedVars = map normalizeVar indexedVars + +-- | indent string with N spaces and transform to Doc. +indent :: Int -> String -> Doc +indent size str = text (indentStr size str) + +-- | get used tokens represented as cats +getAllTokenCats :: CF -> [Cat] +getAllTokenCats cf = map TokenCat (literals cf) + +-- | get TS type names for all tokens +getAllTokenTypenames :: CF -> [String] +getAllTokenTypenames cf = map catToSwiftType (getAllTokenCats cf) + +catToSwiftType :: Cat -> String +catToSwiftType (ListCat c) = "[" ++ catToSwiftType c ++ "]" +catToSwiftType (TokenCat c) = toMixedCase (c ++ "Token") +catToSwiftType cat = toMixedCase (catToStr cat) + +-- | generate name for function which will build node for some cat. +mkBuildFnName :: Cat -> String +mkBuildFnName cat = "build" ++ firstUpperCase (restName cat) + where + restName cat = case cat of + ListCat cat -> restName cat ++ "List" + TokenCat cat -> cat ++ "Token" + otherCat -> catToStr otherCat + +-- | we don't need to declare nodes, which will represent list +-- because they will be referenced directly with TS type Array. +getAbsynWithoutLists :: CF -> [Data] +getAbsynWithoutLists = filter (not . isList . fst) . getAbstractSyntax + +-- | produces a type name for rule label +mkTypeName :: String -> String +mkTypeName = mkName reservedKeywords OrigCase + +-- -- | generate name for function which will interpret node for some cat. +-- mkInterpretFnName :: Cat -> String +-- mkInterpretFnName cat = "interpret" ++ firstUpperCase (restName cat) +-- where +-- restName cat = case cat of +-- ListCat cat -> restName cat ++ "List" +-- TokenCat cat -> cat ++ "Token" +-- otherCat -> catToStr otherCat \ No newline at end of file diff --git a/source/src/BNFC/Options.hs b/source/src/BNFC/Options.hs index ac5fdbf6..db33d22d 100644 --- a/source/src/BNFC/Options.hs +++ b/source/src/BNFC/Options.hs @@ -14,6 +14,7 @@ module BNFC.Options , AlexVersion(..), HappyMode(..), OCamlParser(..), JavaLexerParser(..) , RecordPositions(..), TokenText(..) , Ansi(..) + , AntlrTarget(..) , InPackage , removedIn290 , translateOldOptions @@ -63,8 +64,11 @@ data Mode data Target = TargetC | TargetCpp | TargetCppNoStl | TargetHaskell | TargetHaskellGadt | TargetLatex | TargetJava | TargetOCaml | TargetPygments + | TargetAntlr | TargetTreeSitter | TargetCheck + | TargetDart + | TargetSwift deriving (Eq, Bounded, Enum, Ord) -- | List of Haskell target. @@ -82,7 +86,10 @@ instance Show Target where show TargetOCaml = "OCaml" show TargetPygments = "Pygments" show TargetTreeSitter = "Tree-sitter" + show TargetDart = "Dart" + show TargetSwift = "Swift" show TargetCheck = "Check LBNF file" + show TargetAntlr = "ANTLRv4" -- | Which version of Alex is targeted? data AlexVersion = Alex3 @@ -111,6 +118,23 @@ data Ansi = Ansi | BeyondAnsi -- | Package name (C++ and Java backends). type InPackage = Maybe String +-- | ANTLRv4 targets +data AntlrTarget = CPP | CSharp | Dart | Java | JS | PHP | Python3 | Swift | TS | Go + deriving (Eq, Ord, Show) + +mkAntlrTarget :: String -> AntlrTarget +mkAntlrTarget "java" = Java +mkAntlrTarget "cpp" = CPP +mkAntlrTarget "typescript" = TS +mkAntlrTarget "javascript" = JS +mkAntlrTarget "dart" = Dart +mkAntlrTarget "go" = Go +mkAntlrTarget "php" = PHP +mkAntlrTarget "swift" = Swift +mkAntlrTarget "python" = Python3 +mkAntlrTarget "csharp" = CSharp +mkAntlrTarget _ = Java + -- | How to represent token content in the Haskell backend? data TokenText @@ -147,6 +171,16 @@ data SharedOptions = Options --- C# specific , visualStudio :: Bool -- ^ Generate Visual Studio solution/project files. , wcf :: Bool -- ^ Windows Communication Foundation. + --- ANTLRv4 specific + , listener :: Bool + , visitor :: Bool + , wError :: Bool + , dLanguage :: AntlrTarget + , xlog :: Bool + , xDbgST :: Bool + , xDbgSTWait :: Bool + , atn :: Bool + , antlrOpts :: String } deriving (Eq, Ord, Show) -- We take this opportunity to define the type of the backend functions. @@ -181,6 +215,16 @@ defaultOptions = Options -- C# specific , visualStudio = False , wcf = False + -- ANTLRv4 specific + , listener = True + , visitor = False + , wError = False + , dLanguage = Java + , xlog = False + , xDbgST = False + , xDbgSTWait = False + , atn = False + , antlrOpts = "" } -- | Check whether an option is unchanged from the default. @@ -261,7 +305,10 @@ printTargetOption = ("--" ++) . \case TargetOCaml -> "ocaml" TargetPygments -> "pygments" TargetTreeSitter -> "tree-sitter" + TargetDart -> "dart" + TargetSwift -> "swift" TargetCheck -> "check" + TargetAntlr -> "antlr4" printAlexOption :: AlexVersion -> String printAlexOption = ("--" ++) . \case @@ -292,7 +339,7 @@ targetOptions :: [ OptDescr (SharedOptions -> SharedOptions)] targetOptions = [ Option "" ["java"] (NoArg (\o -> o {target = TargetJava})) "Output Java code [default: for use with JLex and CUP]" - , Option "" ["java-antlr"] (NoArg (\ o -> o{ target = TargetJava, javaLexerParser = Antlr4 })) + , Option "" ["java-antlr"] (NoArg (\o -> o {target = TargetJava, javaLexerParser = Antlr4})) "Output Java code for use with ANTLR (short for --java --antlr)" , Option "" ["haskell"] (NoArg (\o -> o {target = TargetHaskell})) "Output Haskell code for use with Alex and Happy (default)" @@ -308,14 +355,20 @@ targetOptions = "Output C++ code (without STL) for use with FLex and Bison" , Option "" ["ocaml"] (NoArg (\o -> o {target = TargetOCaml})) "Output OCaml code for use with ocamllex and ocamlyacc" - , Option "" ["ocaml-menhir"] (NoArg (\ o -> o{ target = TargetOCaml, ocamlParser = Menhir })) + , Option "" ["ocaml-menhir"] (NoArg (\o -> o {target = TargetOCaml, ocamlParser = Menhir})) "Output OCaml code for use with ocamllex and menhir (short for --ocaml --menhir)" , Option "" ["pygments"] (NoArg (\o -> o {target = TargetPygments})) "Output a Python lexer for Pygments" , Option "" ["tree-sitter"] (NoArg (\o -> o {target = TargetTreeSitter})) "Output grammar.js file for use with tree-sitter" - , Option "" ["check"] (NoArg (\ o -> o{target = TargetCheck })) + , Option "" ["check"] (NoArg (\o -> o{target = TargetCheck })) "No output. Just check input LBNF file" + , Option "" ["dart"] (NoArg (\ o -> o{target = TargetDart })) + "Output Dart code for use with ANTLR" + , Option "" ["swift"] (NoArg (\o -> o{target = TargetSwift})) + "Output Swift code for use with ANTLR" + , Option "" ["antlr"] (NoArg (\o -> o {target = TargetAntlr})) + "Output lexer and parser grammars for ANTLRv4" ] -- | A list of the options and for each of them, the target language @@ -390,6 +443,46 @@ specificOptions = , ( Option [] ["agda"] (NoArg (\o -> o { agda = True, tokenText = TextToken })) "Also generate Agda bindings for the abstract syntax" , [TargetHaskell] ) + , (Option [] ["listener"] (NoArg (\o -> o { listener = True })) + "Generate parse tree listener for ANTLR result. True by default" + , [TargetAntlr]) + , (Option [] ["no-listener"] (NoArg (\o -> o { listener = False })) + "Do NOT generate parse tree listener" + , [TargetAntlr]) + , (Option [] ["visitor"] (NoArg (\o -> o { visitor = True })) + "Generate parse tree visitor for ANTLR result. False by default" + , [TargetAntlr]) + , (Option [] ["no-visitor"] (NoArg (\o -> o { visitor = False })) + "Do NOT generate parse tree visitor" + , [TargetAntlr]) + , (Option [] ["Werror"] (NoArg (\o -> o { wError = True })) + "Make ANTLR treat warnings as errors" + , [TargetAntlr]) + , (Option [] ["language"] (ReqArg (\lang o -> o { dLanguage = mkAntlrTarget lang }) "Dlanguage") + "Specify target language for ANTLR" + , [TargetAntlr]) + , (Option [] ["Xlog"] (NoArg (\o -> o { xlog = True })) + "Create log file with information of grammar processing" + , [TargetAntlr]) + , (Option [] ["XdbgST"] (NoArg (\o -> o { xDbgST = True })) $ unlines + [ "Open window with generated code and templates used to generate this code" + , "It invokes the StringTemplate inspector window." + ] + , [TargetAntlr]) + , (Option [] ["XdbgSTWait"] (NoArg (\o -> o { xDbgSTWait = True })) + "Wait for ST visualizer to close before continuing" + , [TargetAntlr]) + , (Option [] ["atn"] (NoArg (\o -> o { atn = True })) $ unlines + [ "Generate DOT graph files that represent the internal ATN (augmented transition network) data structures that ANTLR uses to represent grammars." + , "The files come out as Grammar.rule .dot. If the grammar is a combined grammar, the lexer rules are named Grammar Lexer.rule .dot." + ] + , [TargetAntlr]) + , (Option [] ["opts"] (ReqArg (\strOpts o -> o { antlrOpts = strOpts }) "OPTIONS") $ unlines + [ "String of ANTLRv4 options which will be directly embedded to Makefile ANTLR call" + , "Options from this string override directly specified options" + , "Usage: --opts=\"-no-listener -visitor -Xlog\"" + ] + , [TargetAntlr]) ] -- | The list of specific options for a target. @@ -452,7 +545,7 @@ help = unlines $ title ++ , usageInfo "TARGET languages" targetOptions ] ++ map targetUsage helpTargets where - helpTargets = [ TargetHaskell, TargetJava, TargetC, TargetCpp ] + helpTargets = [ TargetHaskell, TargetJava, TargetC, TargetCpp, TargetAntlr ] targetUsage t = usageInfo (printf "Special options for the %s backend" (show t)) (specificOptions' t) @@ -530,7 +623,10 @@ instance Maintained Target where TargetOCaml -> True TargetPygments -> True TargetTreeSitter -> True + TargetDart -> True + TargetSwift -> True TargetCheck -> True + TargetAntlr -> True instance Maintained AlexVersion where printFeature = printAlexOption @@ -638,6 +734,7 @@ translateOldOptions = mapM $ \ o -> do [ ("-agda" , "--agda") , ("-java" , "--java") , ("-java1.5" , "--java") + , ("-swift" , "--swift") , ("-c" , "--c") , ("-cpp" , "--cpp") , ("-cpp_stl" , "--cpp")