(.using [library [lux (.except Module Code) ["@" target] [abstract [predicate (.only Predicate)] ["[0]" monad (.only Monad do)]] [control ["[0]" maybe] ["[0]" try (.only Try)] ["[0]" exception (.only exception:)] [concurrency ["[0]" async (.only Async) ("[1]#[0]" monad)]]] [data [binary (.only Binary)] ["[0]" text ("[1]#[0]" hash) ["%" format (.only format)] [encoding ["[0]" utf8]]] [collection ["[0]" dictionary (.only Dictionary)] ["[0]" list]]] [world ["[0]" file]]]] ["[0]" // (.only Context Code) ["/[1]" // "_" [import (.only Import)] ["/[1]" // (.only Input)] [archive [module [descriptor (.only Module)]]]]]) (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: .public 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_once 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 [! (is (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))) (is Enumeration (dictionary.empty text.hash)) contexts)))