(.module: [library [lux {"-" [Module Code]} ["@" target] [abstract [predicate {"+" [Predicate]}] ["." monad {"+" [Monad do]}]] [control ["." maybe] ["." try {"+" [Try]}] ["." exception {"+" [exception:]}] [concurrency ["." async {"+" [Async]} ("#\." monad)]]] [data [binary {"+" [Binary]}] ["." text ("#\." hash) ["%" format {"+" [format]}] [encoding ["." utf8]]] [collection ["." dictionary {"+" [Dictionary]}] ["." list]]] [world ["." file]]]] [program [compositor [import {"+" [Import]}]]] ["." // {"+" [Context Code]} ["/#" // "_" [archive [descriptor {"+" [Module]}]] ["/#" // {"+" [Input]}]]]) (exception: .public (cannot_find_module {importer Module} {module Module}) (exception.report ["Module" (%.text module)] ["Importer" (%.text importer)])) (exception: .public (cannot_read_module {module Module}) (exception.report ["Module" (%.text module)])) (type: .public Extension Text) (def: lux_extension Extension ".lux") (def: .public (path fs context module) (All (_ m) (-> (file.System m) Context Module file.Path)) (|> module (//.safe fs) (format context (\ fs separator)))) (def: (find_source_file fs importer contexts module extension) (-> (file.System Async) Module (List Context) Module Extension (Async (Try file.Path))) (case contexts #.End (async\in (exception.except ..cannot_find_module [importer module])) (#.Item context contexts') (let [path (format (..path fs context module) extension)] (do async.monad [? (\ fs file? path)] (if ? (in (#try.Success path)) (find_source_file fs importer contexts' module extension)))))) (def: (full_host_extension partial_host_extension) (-> Extension Extension) (format partial_host_extension ..lux_extension)) (def: (find_local_source_file fs importer import contexts partial_host_extension module) (-> (file.System Async) Module Import (List Context) Extension Module (Async (Try [file.Path Binary]))) ... Preference is explicitly being given to Lux files that have a host extension. ... Normal Lux files (i.e. without a host extension) are then picked as fallback files. (do {! async.monad} [outcome (..find_source_file fs importer contexts module (..full_host_extension partial_host_extension))] (case outcome (#try.Success path) (|> path (\ fs read) (\ (try.with !) each (|>> [path]))) (#try.Failure _) (do {! (try.with !)} [path (..find_source_file fs importer contexts module ..lux_extension)] (|> path (\ fs read) (\ ! each (|>> [path]))))))) (def: (find_library_source_file importer import partial_host_extension module) (-> Module Import Extension Module (Try [file.Path Binary])) (let [path (format module (..full_host_extension partial_host_extension))] (case (dictionary.value path import) (#.Some data) (#try.Success [path data]) #.None (let [path (format module ..lux_extension)] (case (dictionary.value path import) (#.Some data) (#try.Success [path data]) #.None (exception.except ..cannot_find_module [importer module])))))) (def: (find_any_source_file fs importer import contexts partial_host_extension module) (-> (file.System Async) Module Import (List Context) Extension Module (Async (Try [file.Path Binary]))) ... Preference is explicitly being given to Lux files that have a host extension. ... Normal Lux files (i.e. without a host extension) are then picked as fallback files. (do {! async.monad} [outcome (find_local_source_file fs importer import contexts partial_host_extension module)] (case outcome (#try.Success [path data]) (in outcome) (#try.Failure _) (in (..find_library_source_file importer import partial_host_extension module))))) (def: .public (read fs importer import contexts partial_host_extension module) (-> (file.System Async) Module Import (List Context) Extension Module (Async (Try Input))) (do (try.with async.monad) [[path binary] (..find_any_source_file fs importer import contexts partial_host_extension module)] (case (\ utf8.codec decoded binary) (#try.Success code) (in [#////.module module #////.file path #////.hash (text\hash code) #////.code code]) (#try.Failure _) (async\in (exception.except ..cannot_read_module [module]))))) (type: .public Enumeration (Dictionary file.Path Binary)) (def: (context_listing fs context directory enumeration) (-> (file.System Async) Context file.Path Enumeration (Async (Try Enumeration))) (do {! (try.with async.monad)} [enumeration (|> directory (\ fs directory_files) (\ ! each (monad.mix ! (function (_ file enumeration) (if (text.ends_with? ..lux_extension file) (do ! [source_code (\ fs read file)] (async\in (dictionary.has' (text.replaced context "" file) source_code enumeration))) (in enumeration))) enumeration)) (\ ! conjoint))] (|> directory (\ fs sub_directories) (\ ! each (monad.mix ! (context_listing fs context) enumeration)) (\ ! conjoint)))) (def: Action (type (All (_ a) (Async (Try a))))) (def: (canonical fs context) (-> (file.System Async) Context (Action Context)) (do (try.with async.monad) [subs (\ fs sub_directories context)] (in (|> subs list.head (maybe.else context) (file.parent fs) (maybe.else context))))) (def: .public (listing fs contexts) (-> (file.System Async) (List Context) (Action Enumeration)) (let [! (: (Monad Action) (try.with async.monad))] (monad.mix ! (function (_ context enumeration) (do ! [context (..canonical fs context)] (..context_listing fs (format context (\ fs separator)) context enumeration))) (: Enumeration (dictionary.empty text.hash)) contexts)))