diff --git a/.gitignore b/.gitignore index 2349edd..b66981f 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ Thumbs.db Desktop.ini -*.versionsBackup \ No newline at end of file +*.versionsBackup + +# Ignore Intellij files +*.iml \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2ff8be4..db70952 100644 --- a/pom.xml +++ b/pom.xml @@ -3,16 +3,15 @@ 4.0.0 frege frege-repl - 1.0.1 + 1.0.2-SNAPSHOT jar frege-repl https://github.com/Frege/frege-repl UTF-8 src/main/frege - frege-3.21.232-g7b05453 1.0.5 - 1.0.0 + 1.0.2-SNAPSHOT 2.10 4.2.2 4.11 diff --git a/src/main/frege/frege/repl/FregeRepl.fr b/src/main/frege/frege/repl/FregeRepl.fr index ec100a6..6ffd71f 100644 --- a/src/main/frege/frege/repl/FregeRepl.fr +++ b/src/main/frege/frege/repl/FregeRepl.fr @@ -1,23 +1,30 @@ module frege.repl.FregeRepl where -import frege.compiler.Data -import frege.compiler.Import +import Compiler.Data +import Compiler.Import import frege.Version -import frege.java.Net +import Java.Net import Data.List -import frege.interpreter.FregeScriptCompiler hiding(compile) -import frege.interpreter.FregeInterpreter -import frege.compiler.Data(Global) -import frege.compiler.EclipseUtil as EU() +import Interpreter.FregeScriptCompiler hiding(compile) +import Interpreter.FregeInterpreter +import Compiler.Utilities as U() +import Compiler.EclipseUtil as EU() +import Compiler.DocUtils as DU(docit, docSym, DL, Doc, emitHtml) +import Java.Swing (JFrame, invokeLater) +import Java.Awt (Component, ActionListener) +import Repl.Gui data ReplState = ReplState { lastExternalScript :: Maybe String, + lastJavaSource :: Maybe String, prompt :: String } data Repl a = Repl {un :: StateT ReplState Interpreter a} where get = Repl $ StateT (\s -> return (s, s)) - put s = Repl $ StateT (\_ -> return ((), s)) + put s = Repl $ StateT (\_ -> return ((), s)) + modify f = Repl $ StateT (\s -> return ((), f s)) + run repl state = evalStateT (Repl.un repl) state instance Monad Repl where return = Repl . return @@ -47,7 +54,8 @@ evalScript (Eval line) = do oldInterpState <- Repl . lift $ Interpreter.get fregecRes <- Repl . lift $ compile line case fregecRes of - CompilationSuccess (c@CompilationInfo{sourceInfo=sourceInfo,state=g}) -> + CompilationSuccess (c@CompilationInfo{javaSource=javaSrc, sourceInfo=sourceInfo,state=g}) -> do + Repl.modify $ ReplState.{lastJavaSource = Just javaSrc} case sourceInfo of Module -> return $ ReplSuccess $ "Loaded module: " ++ modName where modName = moduleName g @@ -57,7 +65,7 @@ evalScript (Eval line) = do Repl . lift . Interpreter.put $ s.{currentScript <- (++ newLine ++ line)} return NoOutput Expression{variableName=var} -> do - res <- Repl . lift $ evalShow line + res <- Repl . lift $ showVariable line var g Repl . lift $ Interpreter.put oldInterpState case res of Left err -> return $ ReplError err @@ -116,7 +124,12 @@ evalScript (Load filePath) = do return res _ -> return res Left err -> return $ ReplError [err] - + +evalScript Java = do + state <- Repl.get + showgui s = liftIO $ Runnable.new (javaSourceGUI s) >>= invokeLater >> return NoOutput + maybe (return NoOutput) showgui state.lastJavaSource + evalScript Reload = do state <- Repl.get case state.lastExternalScript of @@ -135,10 +148,87 @@ evalScript Reset = do evalScript Skip = return NoOutput evalScript Help = return $ ReplSuccess help + +evalScript (HelpDoc source) = do + fregecRes <- Repl . lift $ compile "\"\"" -- Import current definitions + let response = maybe (return errmsg) (\m -> liftIO (gui m) >> return NoOutput) + errmsg = ReplError [source ++ " cannot be resolved!"] + gui content = invokeLater =<< Runnable.new (helpGUI source content) + case fregecRes of + CompilationSuccess (c@CompilationInfo{state=global}) -> oneof [helpdoc, packDoc] where + helpdoc = fst $ StG.run (helpDoc source) global + packDoc = fst $ StG.run (packDocumentation source) global + oneof = response . listToMaybe . catMaybes + _ -> response $ Just "" + evalScript Version = Repl.get >>= (\s -> return $ ReplSuccess version) evalScript DefMulti = return NoOutput evalScript Quit = return NoOutput +sNameToQName sName = do + g <- getST + qNames <- U.resolve (VName g.thisPack) Position.null sName + return (listToMaybe qNames) -- Just taking first resolved + +helpDoc :: String -> StG (Maybe String) +helpDoc source = do + global <- getST + qNameMay <- sNameToQName (createSName source) + let symMay = qNameMay >>= (\qname -> qname.findit global) + maybe (return Nothing) (\sym -> symbolDocumentation sym >>= return . Just) symMay + +createSName s + | Just (Just ns : Just ty : Just id : _) <- s `match` #^(.*)\.(.*)\.(\p{Lu}.*)$# = with2con ns ty id + | Just (Just ns : Just ty : Just id : _) <- s `match` #^(.*)\.(.*)\.(.*)$# = with2var ns ty id + | Just (Just ty : Just id : _) <- s `match` #^(.*)\.(\p{Lu}.*)$# = with1con ty id + | Just (Just ty : Just id : _) <- s `match` #^(.*)\.(.*)$# = with1var ty id + | s ~ #^\p{Lu}.*$# = Simple (Token CONID s 1 0 0 []) + | otherwise = Simple (Token VARID s 1 0 0 []) + where with2con ns ty id = With2 (qual ns) (qual ty) (con id) + with2var ns ty id = With2 (qual ns) (qual ty) (var id) + with1con ty id = With1 (qual ty) (con id) + with1var ty id = With1 (qual ty) (var id) + qual name = Token QUALIFIER name 1 0 0 [] + con name = Token CONID name 1 0 0 [] + var name = Token VARID name 1 0 0 [] + +symbolDocumentation :: Symbol -> StG String +symbolDocumentation sym = do + sw <- doio $ StringWriter.new () + p <- doio $ StringWriter.printer sw + changeST Global.{gen <- GenSt.{printer=p}} + g <- getST + let ds = docSym g sym + dl = DL (Just "func") [ds] + doc = Doc [dl] + emitHtml false doc -- html without CSS, swing does not understand + doio $ g.printer.close + result <- doio $ sw.toString + return result + +packDocumentation :: String -> StG (Maybe String) +packDocumentation pack = do + g <- getST + r <- getFP pack + case r of + Right (Just fp) -> case fp.doc of + "" -> return . Just $ ("Undocumented package " ++ pack) + text -> do + let doc = Doc $ docit g (Just text) + sw <- doio $ StringWriter.new () + p <- doio $ StringWriter.printer sw + changeST Global.{gen <- GenSt.{printer=p}} + emitHtml false doc -- html without CSS, swing does not understand + doio $ g.printer.close + result <- doio $ sw.toString + return . Just $ result + Left ex -> return Nothing + sonst -> return . Just $ ("(java class?) " ++ pack) + +match s regex = groups <$> s =~ regex where groups m = [m.group i | i <- [1..groupCount m]] + +pure native groupCount :: Matcher -> Int + ioException :: IOException -> Repl ReplResult ioException e = return $ ReplError [Throwable.getMessage e] @@ -170,7 +260,7 @@ showVariable source var g = do case symbolMay of Nothing -> return . Left $ ["Not found: " ++ var] Just symbol -> showSymbol source var symbol g - + showSymbol source var symbol g | isVariable g symbol = do state <- Interpreter.get if isString g symbol then do @@ -179,7 +269,7 @@ showSymbol source var symbol g | isVariable g symbol = do Left err -> return $ Left [err.getMessage] Right value -> return . Right $ toString value else do - Interpreter.put $ state.{currentScript <- (++ newLine ++ var ++ " = " ++ source)} + Interpreter.put $ state.{currentScript <- (++ newLine ++ (variableDeclScript var source))} let showScript = buildShowScript var g symbol showResult <- evalShow showScript return showResult @@ -217,18 +307,20 @@ positionAndName a b = case Symbol.pos a <=> Symbol.pos b of Eq -> comparing (QName.base • Symbol.name) a b ne -> ne -cmdHelp = [(":type ", "Display the type of an expression"), - (":{", "Start multiline definitions"), - (":}", "End multiline defintions"), +cmdHelp = [(":type ", "Display the type of an expression."), (":browse ", "Display the names in a module if " ++ - "a module name is provided otherwise display the names in the default REPL module"), - (":load ", "Load Frege code snippets from an URL or file"), - (":r", "Reload the last script file"), - (":history", "Display the source history for definitions in the default REPL module"), - (":reset", "Reset the session discarding all evaluated scripts"), - (":version", "Display Frege version"), - (":q or :quit", "Quit REPL"), - (":help", "Display this help message")] + "a module name is provided otherwise display the names in the default REPL module."), + (":load ", "Load Frege code snippets from an URL or file."), + (":java", "View Java translation of last compiled Frege source."), + (":r", "Reload the last script file."), + (":history", "Display the source history for definitions in the default REPL module."), + (":reset", "Reset the session discarding all evaluated scripts."), + (":version", "Display Frege version."), + (":{", "Start multiline definitions."), + (":}", "End multiline defintions."), + (":help ", "Display the documentation for the given name." ++ + " If the name is not provided, display this help message."), + (":q or :quit", "Quit REPL")] f `on` g = \x \y -> f (g x) (g y) @@ -241,7 +333,7 @@ renderCmdHelp cmdHelp = map render cmdHelp where help = intercalate newLine $ header ++ body where header = ["At the prompt, you can enter Frege code snippets to get them evaluated.", "The output or compilation errors will be printed below the prompt.", - "In addition to Frege code, the following commands are supported:"] + "In addition to Frege code, the following commands are supported:\n"] body = renderCmdHelp cmdHelp runFile :: String -> Repl () @@ -283,26 +375,88 @@ urlContents url = do inStream <- URL.openStream url scanner <- Scanner.new inStream scanner.useDelimiter "\\Z" - scanner.next + scanner.next `finally` scanner.close fileContents filePath = do file <- File.new filePath scanner <- Scanner.fromFile file "utf-8" scanner.useDelimiter "\\Z" - scanner.next + scanner.next `finally` scanner.close data Scanner = mutable native java.util.Scanner where native new :: InputStream -> IO Scanner native fromFile new :: MutableIO File -> String -> IO Scanner throws FileNotFoundException native useDelimiter :: Scanner -> String -> IO () native next :: Scanner -> IO String - -runRepl repl state = evalStateT (Repl.un repl) state -pure native isEmpty :: String -> Bool +javaSourceGUI :: String -> IO () +javaSourceGUI javaSource = do + frame::JFrame <- JFrame.new "Java Source" + newContentPane::JPanel <- BorderLayout.new () >>= JPanel.new + frame.setDefaultCloseOperation JFrame.dispose_on_close + editor::JEditorPane <- JEditorPane.new "text/plain" javaSource + editor.setEditable false + scrollPane <- JScrollPane.new editor + dimension <- Dimension.new 600 600 + JComponent.setPreferredSize scrollPane dimension + Container.add newContentPane scrollPane (asObject BorderLayout.center) + frame.setContentPane newContentPane + frame.pack + frame.setVisible true + + +helpGUI :: String -> String -> IO () +helpGUI title content = do + frame <- JFrame.new (title ++ " - Documentation") + frame.setDefaultCloseOperation JFrame.dispose_on_close + newContentPane:: JPanel <- BorderLayout.new () >>= JPanel.new + let html :: String + html = substring content $ indexOf content "" -- Skip DOCTYPE + editor::JEditorPane <- JEditorPane.new "text/html" html + editor.setEditable false + helpLinksListener <- FregeJavaProxy.with showDesktop HyperlinkListener.clazz + editor.addHyperlinkListener helpLinksListener + scrollPane <- JScrollPane.new editor + dimension <- Dimension.new 600 600 + JComponent.setPreferredSize scrollPane dimension + Container.add newContentPane scrollPane (asObject BorderLayout.center) + newContentPane.setOpaque true + frame.setContentPane newContentPane + frame.pack + frame.setVisible true + +data FregeJavaProxy = pure native frege.memoryjavac.FregeJavaProxy where + native with frege.memoryjavac.FregeJavaProxy.with :: (Object -> Method -> ObjectArr -> ST s a) -> Class c -> IO c + +showDesktop :: Object -> Method -> ObjectArr -> IO () +showDesktop _ _ args = do + event <- return $ asHyperlinkEvent $ args.elemAt 0 + d <- Desktop.getDesktop () + desktopSupported <- d.isDesktopSupported + let url = HyperlinkEvent.getURL event + navigateRelative = either throw d.browse . URI.new $ fixHelpLink event.getDescription + navigate = maybe navigateRelative (\u -> toURI u >>= d.browse) url + when (event.getEventType == HyperlinkEvent_EventType.activated) navigate + + +fixHelpLink (m~#^\.\.(.*)$#) = maybe helpRoot (\s -> helpRoot ++ s) $ m.group 1 +fixHelpLink s = s + +helpRoot = "http://try.frege-lang.org/doc" + +native toURI :: URL -> ST s URI throws URISyntaxException +pure native asObject "(java.lang.Object)" :: a -> Object -data Command = Version | Help | Browse | BrowseModule String | Type String | Load String | Eval String | - DefMulti | History | Reload | Reset | Skip | Quit where + +pure native isEmpty :: String -> Bool +pure native replaceAll :: String -> String -> String -> String +pure native indexOf :: String -> String -> Int +pure native substring :: String -> Int -> String + | String -> Int -> Int -> String + +data Command = Version | Help | HelpDoc String | Browse | + BrowseModule String | Type String | Load String | Eval String | + Java | DefMulti | History | Reload | Reset | Skip | Quit where parse :: String -> Either String Command parse s = parse' $ trim s where @@ -315,11 +469,13 @@ data Command = Version | Help | Browse | BrowseModule String | Type String | Loa parseCommand (m~#^:l.*?\s+(.*)#) = maybe (Left "Missing external script path!") (Right . Load) $ m.group 1 parseCommand ":version" = Right Version + parseCommand (m~#:help\s+(.*)#) = maybe (Right Help) (Right . HelpDoc) $ m.group 1 parseCommand ":help" = Right Help - parseCommand (m~#^:t.*?\s+(.*)#) = + parseCommand (m~#^:t.*?\s+(.*)#) = maybe (Left "Missing expression!") (Right . Type) $ m.group 1 parseCommand ":{" = Right DefMulti parseCommand ":history" = Right History + parseCommand ":java" = Right Java parseCommand ":r" = Right Reload parseCommand ":reset" = Right Reset parseCommand ":q" = Right Quit @@ -332,6 +488,7 @@ derive Show Command initReplState :: ReplState initReplState = ReplState{ lastExternalScript = Nothing, + lastJavaSource = Nothing, prompt = "frege> " --Default prompt } @@ -340,14 +497,13 @@ initInterpreterState = do urlarr <- URLArray.new 0 loader <- ClassLoader.current >>= URLClassLoader.new urlarr classes <- HashMap.new () - time <- currentTimeMillis () - let history = "" + time <- System.currentTimeMillis () let interpreterState = InterpreterState { loader = loader, classes = classes, - moduleName = "script.Main" ++ show time, + moduleName = "repl" ++ show time ++ ".Repl", currentScript = "", - modulePrelude = "" + transformDefs = id } return interpreterState @@ -356,7 +512,6 @@ vmName = maybe "" id $ System.getProperty "java.vm.name" javaVersion = maybe "" id $ System.getProperty "java.version" pure native toString :: Object -> String -native currentTimeMillis java.lang.System.currentTimeMillis :: () -> IO Long welcome = "Welcome to Frege " ++ version ++ " (" ++ vmVendor ++ " " ++ vmName ++ ", " ++ javaVersion ++ ")" @@ -368,12 +523,12 @@ printResult _ NoOutput = return () main [file] = do interpreterState <- initInterpreterState - evalInterpreter (runRepl (runFile file) initReplState) interpreterState + evalInterpreter (Repl.run (runFile file) initReplState) interpreterState main _ = do console <- ConsoleReader.new () console.setPrompt initReplState.prompt console.setExpandEvents false console.println welcome interpreterState <- initInterpreterState - evalInterpreter (runRepl (repl console) initReplState) interpreterState - + evalInterpreter (Repl.run (repl console) initReplState) interpreterState + System.exit 0 diff --git a/src/main/frege/frege/repl/Gui.fr b/src/main/frege/frege/repl/Gui.fr new file mode 100644 index 0000000..05db45a --- /dev/null +++ b/src/main/frege/frege/repl/Gui.fr @@ -0,0 +1,88 @@ +module frege.repl.Gui where + +import Java.Net (URI, URL) +import Java.Awt (Component) + +pure native asHyperlinkListener "(javax.swing.event.HyperlinkListener)" :: Object -> HyperlinkListener +pure native asHyperlinkEvent "(javax.swing.event.HyperlinkEvent)" :: Object -> HyperlinkEvent + + +native setSize :: Component -> Int -> Int -> IO () + +data Desktop = mutable native java.awt.Desktop where + native getDesktop java.awt.Desktop.getDesktop :: () -> IO Desktop + native isDesktopSupported :: Desktop -> IO Bool + native browse :: Desktop -> URI -> IO () throws IOException + +data HyperlinkEvent = pure native javax.swing.event.HyperlinkEvent where + pure native getURL :: HyperlinkEvent -> Maybe URL + pure native getDescription :: HyperlinkEvent -> String + pure native getEventType :: HyperlinkEvent -> HyperlinkEvent_EventType + +data HyperlinkEvent_EventType = pure native javax.swing.event.HyperlinkEvent.EventType where + pure native entered javax.swing.event.HyperlinkEvent.EventType.ENTERED :: HyperlinkEvent_EventType + pure native exited javax.swing.event.HyperlinkEvent.EventType.EXITED :: HyperlinkEvent_EventType + pure native activated javax.swing.event.HyperlinkEvent.EventType.ACTIVATED :: HyperlinkEvent_EventType + pure native equals :: HyperlinkEvent_EventType -> Object -> Bool + pure native hashCode :: HyperlinkEvent_EventType -> Int + pure native toString :: HyperlinkEvent_EventType -> String + +data BorderLayout = mutable native java.awt.BorderLayout where + native new :: () -> IO BorderLayout + | Int -> Int -> IO BorderLayout + pure native north java.awt.BorderLayout.NORTH :: String + pure native south java.awt.BorderLayout.SOUTH :: String + pure native east java.awt.BorderLayout.EAST :: String + pure native west java.awt.BorderLayout.WEST :: String + pure native center java.awt.BorderLayout.CENTER :: String + pure native before_first_line java.awt.BorderLayout.BEFORE_FIRST_LINE :: String + pure native after_last_line java.awt.BorderLayout.AFTER_LAST_LINE :: String + pure native before_line_begins java.awt.BorderLayout.BEFORE_LINE_BEGINS :: String + pure native after_line_ends java.awt.BorderLayout.AFTER_LINE_ENDS :: String + pure native page_start java.awt.BorderLayout.PAGE_START :: String + pure native page_end java.awt.BorderLayout.PAGE_END :: String + pure native line_start java.awt.BorderLayout.LINE_START :: String + pure native line_end java.awt.BorderLayout.LINE_END :: String + +data Container = mutable native java.awt.Container where + native add :: Container -> Component -> IO Component + | Container -> Component -> Object -> IO () + + native setLayout :: Container -> LayoutManager -> IO () + +data JPanel = mutable native javax.swing.JPanel where + native new :: () -> IO JPanel + | LayoutManager -> IO JPanel + +data LayoutManager = mutable native java.awt.LayoutManager + +data JScrollPane = mutable native javax.swing.JScrollPane where + native new :: Component -> IO JScrollPane + native setVerticalScrollBarPolicy :: JScrollPane -> Int -> IO () + native setHorizontalScrollBarPolicy :: JScrollPane -> Int -> IO () + pure native horizontal_scrollbar_as_needed javax.swing.JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED :: Int + pure native vertical_scrollbar_always javax.swing.JScrollPane.VERTICAL_SCROLLBAR_ALWAYS :: Int + pure native horizontal_scrollbar_always javax.swing.JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS :: Int + +data JComponent = mutable native javax.swing.JComponent where + native setPreferredSize :: JComponent -> Dimension -> IO () + +data Dimension = mutable native java.awt.Dimension where + native new :: Int -> Int -> IO Dimension + +data JTextArea = mutable native javax.swing.JTextArea where + native new :: String -> Int -> Int -> IO JTextArea + +instance Eq HyperlinkEvent_EventType where + e1 == e2 = e1.equals $ asObject e2 + +data HyperlinkListener = pure native javax.swing.event.HyperlinkListener where + native hyperlinkUpdate :: HyperlinkListener -> HyperlinkEvent -> IO () + native clazz "javax.swing.event.HyperlinkListener.class" :: Class HyperlinkListener + +data JEditorPane = mutable native javax.swing.JEditorPane where + native new :: String -> String -> IO JEditorPane + native addHyperlinkListener :: JEditorPane -> HyperlinkListener -> IO () + native setEditable :: JEditorPane -> Bool -> IO () + +private pure native asObject "(java.lang.Object)" :: a -> Object \ No newline at end of file