REPL namespaces; lib function for pattern matching
All checks were successful
Test, Build, and Release / test (push) Successful in 1m52s
Test, Build, and Release / build (push) Successful in 1m20s

Adds support for REPL namespacing, primarily to avoid `main` collisions.
Also adds a library function for an ergonomic pattern matching function
that I've been noodling on. I might explore ways to make list syntax
less annoying specifically for pattern matching like this.
This commit is contained in:
2025-02-02 10:50:28 -06:00
parent 1a9a4494e0
commit f9864b8361
11 changed files with 165 additions and 81 deletions

View File

@ -6,6 +6,7 @@ import Parser
import Research
import Data.List (partition)
import Data.Maybe (mapMaybe)
import Control.Monad (foldM)
import System.IO
import System.FilePath (takeDirectory, normalise, (</>))
@ -13,6 +14,26 @@ import System.FilePath (takeDirectory, normalise, (</>))
import qualified Data.Map as Map
import qualified Data.Set as Set
extractMain :: Env -> Either String T
extractMain env =
case Map.lookup "main" env of
Just result -> Right result
Nothing -> Left "No `main` function detected"
processImports :: Set.Set FilePath -> FilePath -> FilePath -> [TricuAST]
-> Either String ([TricuAST], [(FilePath, String, FilePath)])
processImports seen base currentPath asts =
let (imports, nonImports) = partition isImp asts
importPaths = mapMaybe getImportInfo imports
in if currentPath `Set.member` seen
then Left $ "Encountered cyclic import: " ++ currentPath
else Right (nonImports, importPaths)
where
isImp (SImport _ _) = True
isImp _ = False
getImportInfo (SImport p n) = Just (p, n, makeRelativeTo currentPath p)
getImportInfo _ = Nothing
evaluateFileResult :: FilePath -> IO T
evaluateFileResult filePath = do
contents <- readFile filePath
@ -20,11 +41,11 @@ evaluateFileResult filePath = do
case parseProgram tokens of
Left err -> errorWithoutStackTrace (handleParseError err)
Right ast -> do
ast <- preprocessFile filePath
let finalEnv = evalTricu Map.empty ast
case Map.lookup "main" finalEnv of
Just finalResult -> return finalResult
Nothing -> errorWithoutStackTrace "No `main` function detected"
processedAst <- preprocessFile filePath
let finalEnv = evalTricu Map.empty processedAst
case extractMain finalEnv of
Right result -> return result
Left err -> errorWithoutStackTrace err
evaluateFile :: FilePath -> IO Env
evaluateFile filePath = do
@ -50,37 +71,25 @@ preprocessFile :: FilePath -> IO [TricuAST]
preprocessFile p = preprocessFile' Set.empty p p
preprocessFile' :: Set.Set FilePath -> FilePath -> FilePath -> IO [TricuAST]
preprocessFile' s b p
| p `Set.member` s =
errorWithoutStackTrace $ "Encountered cyclic import: " ++ p
| otherwise = do
c <- readFile p
let t = lexTricu c
case parseProgram t of
Left e -> errorWithoutStackTrace (handleParseError e)
Right a -> do
let (i, n) = partition isImp a
let s' = Set.insert p s
r <- concat <$>
mapM (procImp s' "" p) i
pure $ r ++ n
preprocessFile' seen base currentPath = do
contents <- readFile currentPath
let tokens = lexTricu contents
case parseProgram tokens of
Left err -> errorWithoutStackTrace (handleParseError err)
Right ast ->
case processImports seen base currentPath ast of
Left err -> errorWithoutStackTrace err
Right (nonImports, importPaths) -> do
let seen' = Set.insert currentPath seen
imported <- concat <$> mapM (processImportPath seen' base) importPaths
pure $ imported ++ nonImports
where
isImp :: TricuAST -> Bool
processImportPath seen base (path, name, importPath) = do
ast <- preprocessFile' seen base importPath
pure $ map (nsDefinition (if name == "!Local" then "" else name))
$ filter (not . isImp) ast
isImp (SImport _ _) = True
isImp _ = False
procImp :: Set.Set FilePath -> String -> FilePath -> TricuAST -> IO [TricuAST]
procImp s m f (SImport p "!Local") = do
let ip = makeRelativeTo f p
a <- preprocessFile' s b ip
let d = filter (not . isImp) a
pure $ map (nsDefinition m) d
procImp s _ f (SImport p n) = do
let ip = makeRelativeTo f p
a <- preprocessFile' s b ip
let d = filter (not . isImp) a
pure $ map (nsDefinition n) d
procImp _ _ _ _ = error "Unexpected non-import in processImport"
isImp _ = False
makeRelativeTo :: FilePath -> FilePath -> FilePath
makeRelativeTo f i =
@ -94,7 +103,7 @@ nsDefinition :: String -> TricuAST -> TricuAST
nsDefinition "" def = def
nsDefinition moduleName (SDef name args body)
| isPrefixed name = SDef name args (nsBody moduleName body)
| otherwise = SDef (nsVariable moduleName name)
| otherwise = SDef (nsVariable moduleName name)
args (nsBody moduleName body)
nsDefinition moduleName other =
nsBody moduleName other
@ -115,7 +124,7 @@ nsBody moduleName (TStem subtree) =
TStem (nsBody moduleName subtree)
nsBody moduleName (SDef name args body)
| isPrefixed name = SDef name args (nsBody moduleName body)
| otherwise = SDef (nsVariable moduleName name)
| otherwise = SDef (nsVariable moduleName name)
args (nsBody moduleName body)
nsBody _ other = other
@ -125,19 +134,19 @@ nsBodyScoped moduleName args body = case body of
if name `elem` args
then SVar name
else nsBody moduleName (SVar name)
SApp func arg ->
SApp func arg ->
SApp (nsBodyScoped moduleName args func) (nsBodyScoped moduleName args arg)
SLambda innerArgs innerBody ->
SLambda innerArgs innerBody ->
SLambda innerArgs (nsBodyScoped moduleName (args ++ innerArgs) innerBody)
SList items ->
SList items ->
SList (map (nsBodyScoped moduleName args) items)
TFork left right ->
TFork (nsBodyScoped moduleName args left)
TFork left right ->
TFork (nsBodyScoped moduleName args left)
(nsBodyScoped moduleName args right)
TStem subtree ->
TStem subtree ->
TStem (nsBodyScoped moduleName args subtree)
SDef name innerArgs innerBody ->
SDef (nsVariable moduleName name) innerArgs
SDef (nsVariable moduleName name) innerArgs
(nsBodyScoped moduleName (args ++ innerArgs) innerBody)
other -> other