|
| 1 | +{-# LANGUAGE LambdaCase #-} |
| 2 | +{-# LANGUAGE OverloadedStrings #-} |
| 3 | + |
1 | 4 | module Main (main) where
|
2 | 5 |
|
3 |
| -import Parser (interpretFile) |
| 6 | +import Control.Exception (SomeException, catch, evaluate) |
| 7 | +import Control.Monad (foldM) |
| 8 | +import Control.Monad.IO.Class (liftIO) |
| 9 | +import Data.Aeson (ToJSON (..), Value (..), object, (.=)) |
| 10 | +import Data.Char (isSpace) |
| 11 | +import Data.IORef |
| 12 | +import Data.List (dropWhile) |
| 13 | +import qualified Data.Map as Map |
| 14 | +import Data.Text.Lazy (Text, pack, unpack) |
| 15 | +import Data.Text.Lazy.Encoding (decodeUtf8) |
| 16 | +import Evaluator |
| 17 | +import Network.Wai.Middleware.Cors (simpleCors) |
| 18 | +import Network.Wai.Middleware.RequestLogger (logStdoutDev) |
| 19 | +import Parser |
4 | 20 | import System.Environment (getArgs)
|
| 21 | +import System.IO (getLine, hFlush, isEOF, stdout) |
| 22 | +import System.Timeout (timeout) |
| 23 | +import Value |
| 24 | +import Web.Scotty |
| 25 | + |
| 26 | +-- ---------------------------------------------------------------------------- |
| 27 | +-- Web Server Specific Definitions |
| 28 | +-- ---------------------------------------------------------------------------- |
| 29 | + |
| 30 | +data StepResult = StepResult {output :: String, ast :: Maybe String} deriving (Show) |
| 31 | + |
| 32 | +instance ToJSON StepResult where |
| 33 | + toJSON (StepResult out maybeAst) = object ["output" .= out, "ast" .= maybeAst] |
| 34 | + |
| 35 | +data MultiEvalResult = MultiEvalResult |
| 36 | + { steps :: [StepResult], |
| 37 | + finalError :: Maybe String, |
| 38 | + finalEnvironment :: Env, |
| 39 | + traceLog :: Maybe [String] -- Field for combined trace log |
| 40 | + } |
| 41 | + deriving (Show) |
| 42 | + |
| 43 | +instance ToJSON MultiEvalResult where |
| 44 | + toJSON (MultiEvalResult stepList err envMap mLog) = |
| 45 | + object |
| 46 | + [ "steps" .= stepList, |
| 47 | + "finalError" .= err, |
| 48 | + "finalEnvironment" .= envMap, |
| 49 | + "traceLog" .= mLog |
| 50 | + ] |
| 51 | + |
| 52 | +-- Timeout for evaluation in microseconds (e.g., 5 seconds) |
| 53 | +evaluationTimeoutDuration :: Int |
| 54 | +evaluationTimeoutDuration = 5 * 1000 * 1000 -- 5 seconds |
| 55 | + |
| 56 | +-- Function to process a single line for the web handler's fold |
| 57 | +-- Takes the environment accumulated so far, and the current line string |
| 58 | +-- Returns Either Error Message (New Env, Output String, AST String, Trace Log for this line) |
| 59 | +processWebLine :: Env -> String -> IO (Either String (Env, String, Maybe String, TraceLog)) |
| 60 | +processWebLine currentEnv line = do |
| 61 | + let trimmedLine = dropWhile isSpace line |
| 62 | + if null trimmedLine |
| 63 | + then return $ Right (currentEnv, "", Nothing, []) |
| 64 | + else case parse parseDefinition trimmedLine of |
| 65 | + Just ((name, defExpr), "") -> do |
| 66 | + let astString = Just (show defExpr) |
| 67 | + let envForDefEval = Map.insert name valueForDef currentEnv |
| 68 | + -- evalResultAndTrace now holds Either String (Value, TraceLog) |
| 69 | + evalResultAndTrace = eval envForDefEval defExpr |
| 70 | + valueForDef = case evalResultAndTrace of |
| 71 | + Left e -> error $ "Internal error: accessing value from failed recursive definition for " ++ name ++ ": " ++ e |
| 72 | + Right (v, _) -> v |
| 73 | + case evalResultAndTrace of |
| 74 | + Left err -> return $ Left $ "Error in definition '" ++ name ++ "': " ++ err |
| 75 | + Right (_, trace) -> return $ Right (envForDefEval, "Defined (rec): " ++ name, astString, trace) |
| 76 | + _ -> |
| 77 | + case parse parseExpr trimmedLine of |
| 78 | + Just (exprAST, "") -> do |
| 79 | + let astString = Just (show exprAST) |
| 80 | + case eval currentEnv exprAST of -- eval now returns Either String (Value, TraceLog) |
| 81 | + Left errMsg -> return $ Left errMsg |
| 82 | + Right (val, trace) -> return $ Right (currentEnv, show val, astString, trace) |
| 83 | + _ -> |
| 84 | + return $ Left $ "Parse Error on line: " ++ take 40 trimmedLine |
| 85 | + |
| 86 | +-- Function to run the Scotty Web Server |
| 87 | +runWebServer :: IO () |
| 88 | +runWebServer = do |
| 89 | + putStrLn "Starting L Language Web Server on port 3000 (Stateful, Multi-line, with Trace)..." |
| 90 | + sharedEnvRef <- newIORef initialEnv -- Initialize shared state with built-ins |
| 91 | + scotty 3000 $ do |
| 92 | + middleware simpleCors |
| 93 | + -- middleware logStdoutDev |
| 94 | + |
| 95 | + get "/" $ do |
| 96 | + html $ |
| 97 | + mconcat |
| 98 | + [ "<h1>L Web Interface</h1>", |
| 99 | + "<p>Enter code (definitions or expressions, one per line) below and click Evaluate.</p>", |
| 100 | + "<textarea id='code' rows='10' cols='80' style='font-family:monospace;'></textarea><br/>", |
| 101 | + "<button onclick='evaluateCode()'>Evaluate</button>", |
| 102 | + "<h2>Outputs / Steps:</h2><div id='outputSteps' style='background-color:#f0f0f0; padding:10px; border:1px solid #ccc; min-height:20px; white-space: pre-wrap;'></div>", |
| 103 | + "<h2>Evaluation Trace:</h2><pre id='outputTrace' style='background-color:#e0e0e0; padding:10px; border:1px solid #bbb; max-height: 300px; overflow-y: auto; white-space: pre-wrap;'></pre>", |
| 104 | + "<h2>Final Environment:</h2><pre id='outputEnv' style='background-color:#f0f0f0; padding:10px; border:1px solid #ccc; min-height:20px; max-height: 200px; overflow-y: auto;'></pre>", |
| 105 | + "<script src='/script.js'></script>" |
| 106 | + ] |
| 107 | + |
| 108 | + get "/script.js" $ do |
| 109 | + setHeader "Content-Type" "application/javascript" |
| 110 | + file "frontend.js" |
| 111 | + |
| 112 | + post "/evaluate" $ do |
| 113 | + codeText <- body |
| 114 | + let codeString = unpack (decodeUtf8 codeText) |
| 115 | + let codeLines = lines codeString |
| 116 | + |
| 117 | + initialEnvState <- liftIO $ readIORef sharedEnvRef |
| 118 | + |
| 119 | + -- Accumulator: Either Error (CurrentEnvInBlock, List of StepResults, Accumulated TraceLog) |
| 120 | + finalResult <- |
| 121 | + foldM |
| 122 | + ( \acc line -> case acc of |
| 123 | + Left err -> return $ Left err |
| 124 | + Right (env, stepsAcc, traceAcc) -> do |
| 125 | + lineResult <- liftIO $ processWebLine env line |
| 126 | + case lineResult of |
| 127 | + Left err -> return $ Left err |
| 128 | + Right (newEnv, outputStr, maybeAstStr, lineTrace) -> |
| 129 | + let step = StepResult outputStr maybeAstStr |
| 130 | + in return $ Right (newEnv, if null outputStr && maybeAstStr == Nothing then stepsAcc else stepsAcc ++ [step], traceAcc ++ lineTrace) |
| 131 | + ) |
| 132 | + (Right (initialEnvState, [], [])) |
| 133 | + codeLines |
| 134 | + |
| 135 | + response <- case finalResult of |
| 136 | + Left errorMsg -> |
| 137 | + return $ MultiEvalResult [] (Just errorMsg) initialEnvState Nothing |
| 138 | + Right (finalEnvState, steps, accumulatedTrace) -> do |
| 139 | + liftIO $ atomicModifyIORef' sharedEnvRef $ \_ -> (finalEnvState, ()) |
| 140 | + return $ MultiEvalResult steps Nothing finalEnvState (Just accumulatedTrace) |
| 141 | + |
| 142 | + json response |
| 143 | + |
| 144 | +-- ---------------------------------------------------------------------------- |
| 145 | +-- REPL Specific Definitions |
| 146 | +-- ---------------------------------------------------------------------------- |
| 147 | +repl :: Env -> IO () |
| 148 | +repl currentEnv = do |
| 149 | + putStr "L-Repl> " |
| 150 | + hFlush stdout |
| 151 | + eof <- isEOF |
| 152 | + if eof |
| 153 | + then putStrLn "\nGoodbye!" |
| 154 | + else do |
| 155 | + line <- getLine |
| 156 | + case line of |
| 157 | + ":quit" -> putStrLn "Goodbye!" |
| 158 | + ":env" -> do |
| 159 | + print currentEnv |
| 160 | + repl currentEnv |
| 161 | + _ -> handleInput line currentEnv |
| 162 | + |
| 163 | +handleInput :: String -> Env -> IO () |
| 164 | +handleInput line currentEnv = |
| 165 | + let trimmedLine = dropWhile isSpace line |
| 166 | + in if null trimmedLine |
| 167 | + then repl currentEnv |
| 168 | + else case parse parseDefinition trimmedLine of |
| 169 | + Just ((name, expr), "") -> do |
| 170 | + putStrLn $ "Evaluating potentially recursive definition for '" ++ name ++ "'..." |
| 171 | + let newEnv = Map.insert name value currentEnv |
| 172 | + evalResultAndTrace = eval newEnv expr -- Returns (Value, TraceLog) |
| 173 | + value = case evalResultAndTrace of |
| 174 | + Left err -> Prelude.error $ "Internal error: accessing value from failed recursive eval for " ++ name ++ ": " ++ err |
| 175 | + Right (val, _) -> val |
| 176 | + case evalResultAndTrace of |
| 177 | + Left err -> do |
| 178 | + putStrLn $ "Error in definition '" ++ name ++ "': " ++ err |
| 179 | + repl currentEnv |
| 180 | + Right (_, trace) -> do |
| 181 | + -- Definition succeeded |
| 182 | + putStrLn $ "Defined (rec): " ++ name |
| 183 | + putStrLn "--- Evaluation Trace (Definition) ---" |
| 184 | + mapM_ (putStrLn . (" " ++)) trace |
| 185 | + putStrLn "-------------------------------------" |
| 186 | + repl newEnv |
| 187 | + _ -> |
| 188 | + case parse parseExpr trimmedLine of |
| 189 | + Just (exprAST, "") -> do |
| 190 | + case eval currentEnv exprAST of -- Returns (Value, TraceLog) |
| 191 | + Left err -> putStrLn $ "Error: " ++ err |
| 192 | + Right (val, trace) -> do |
| 193 | + -- Expression evaluation succeeded |
| 194 | + print val |
| 195 | + putStrLn "--- Evaluation Trace ---" |
| 196 | + mapM_ (putStrLn . (" " ++)) trace |
| 197 | + putStrLn "----------------------" |
| 198 | + repl currentEnv |
| 199 | + Just (_, rest) -> do |
| 200 | + putStrLn ("Parse Error: Unexpected input near: '" ++ take 20 rest ++ "...'") |
| 201 | + repl currentEnv |
| 202 | + Nothing -> do |
| 203 | + putStrLn "Parse Error: Invalid input." |
| 204 | + repl currentEnv |
| 205 | + |
| 206 | +runRepl :: IO () |
| 207 | +runRepl = do |
| 208 | + putStrLn "Starting L Language REPL..." |
| 209 | + putStrLn "Define: name = expression (recursion supported)" |
| 210 | + putStrLn "Evaluate: expression (built-ins: map, filter, length)" |
| 211 | + putStrLn "Commands: :quit, :env" |
| 212 | + repl initialEnv |
5 | 213 |
|
| 214 | +-- ---------------------------------------------------------------------------- |
| 215 | +-- Main Entry Point - Selects Mode |
| 216 | +-- ---------------------------------------------------------------------------- |
6 | 217 | main :: IO ()
|
7 | 218 | main = do
|
8 | 219 | args <- getArgs
|
9 | 220 | case args of
|
10 |
| - [filename] -> do |
11 |
| - result <- interpretFile filename |
12 |
| - print result |
13 |
| - _ -> putStrLn "Usage: program <filename>" |
| 221 | + ["-r"] -> runRepl |
| 222 | + ["-w"] -> runWebServer |
| 223 | + _ -> do |
| 224 | + putStrLn "Usage: <program-name> [-r | -w]" |
| 225 | + putStrLn " -r: Run interactive REPL" |
| 226 | + putStrLn " -w: Run web server on port 3000 (Stateful, Multi-line, with Trace)" |
| 227 | + putStrLn "Defaulting to REPL mode." |
| 228 | + runRepl |
0 commit comments