(.module: [lux #* [control monad ["p" parser]] [data [collection [list ("list/." Monoid Monad)]] ["." text ("text/." Equivalence) format] ["E" error]] [macro (#+ with-gensyms) ["." code] ["s" syntax (#+ syntax: Syntax)]] [compiler ["." host]] ["." io] [concurrency ["." process]]]) ## [Types] (type: #export (CLI a) {#.doc "A command-line interface parser."} (p.Parser (List Text) a)) ## [Combinators] (def: #export (run inputs parser) (All [a] (-> (List Text) (CLI a) (E.Error a))) (case (p.run inputs parser) (#E.Success [remaining output]) (case remaining #.Nil (#E.Success output) _ (#E.Error (format "Remaining CLI inputs: " (text.join-with " " remaining)))) (#E.Error error) (#E.Error error))) (def: #export any {#.doc "Just returns the next input without applying any logic."} (CLI Text) (function (_ inputs) (case inputs (#.Cons arg inputs') (#E.Success [inputs' arg]) _ (#E.Error "Cannot parse empty arguments.")))) (def: #export (parse parser) {#.doc "Parses the next input with a parsing function."} (All [a] (-> (-> Text (E.Error a)) (CLI a))) (function (_ inputs) (do E.Monad [[remaining raw] (any inputs) output (parser raw)] (wrap [remaining output])))) (def: #export (this reference) {#.doc "Checks that a token is in the inputs."} (-> Text (CLI Any)) (function (_ inputs) (do E.Monad [[remaining raw] (any inputs)] (if (text/= reference raw) (wrap [remaining []]) (E.fail (format "Missing token: \"" reference "\"")))))) (def: #export (somewhere cli) {#.doc "Given a parser, tries to parse it somewhere in the inputs (i.e. not necessarily parsing the immediate inputs)."} (All [a] (-> (CLI a) (CLI a))) (function (_ inputs) (loop [immediate inputs] (case (p.run immediate cli) (#E.Success [remaining output]) (#E.Success [remaining output]) (#E.Error error) (case immediate #.Nil (#E.Error error) (#.Cons to-omit immediate') (do E.Monad [[remaining output] (recur immediate')] (wrap [(#.Cons to-omit remaining) output]))))))) (def: #export end {#.doc "Ensures there are no more inputs."} (CLI Any) (function (_ inputs) (case inputs #.Nil (#E.Success [inputs []]) _ (#E.Error (format "Unknown parameters: " (text.join-with " " inputs)))))) (def: #export (parameter [short long]) (-> [Text Text] (CLI Text)) (|> ..any (p.after (p.either (..this short) (..this long))) ..somewhere)) ## [Syntax] (type: Program-Args (#Raw Text) (#Parsed (List [Code Code]))) (def: program-args^ (Syntax Program-Args) (p.or s.local-identifier (s.tuple (p.some (p.either (do p.Monad [name s.local-identifier] (wrap [(code.identifier ["" name]) (` any)])) (s.record (p.and s.any s.any))))))) (syntax: #export (program: {args program-args^} body) {#.doc (doc "Defines the entry-point to a program (similar to the \"main\" function/method in other programming languages)." "Can take a list of all the input parameters to the program, or can destructure them using CLI-option combinators from the lux/cli module." (program: all-args (do Monad [foo init-program bar (do-something all-args)] (wrap []))) (program: [name] (io (log! (text/compose "Hello, " name)))) (program: [{config config^}] (do Monad [data (init-program config)] (do-something data))))} (with-gensyms [g!program] (case args (#Raw args) (wrap (list (` ("lux program" (.function ((~ g!program) (~ (code.identifier ["" args]))) ((~! do) (~! io.Monad) [] (~ body))))))) (#Parsed args) (with-gensyms [g!args g!_ g!output g!message] (wrap (list (` ("lux program" (.function ((~ g!program) (~ g!args)) (case ((: (~! (..CLI (io.IO .Any))) ((~! do) (~! p.Monad) [(~+ (|> args (list/map (function (_ [binding parser]) (list binding parser))) list/join)) (~ g!_) ..end] ((~' wrap) ((~! do) (~! io.Monad) [(~ g!output) (~ body) (~+ (`` (for {(~~ (static host.jvm)) (list)} (list g!_ (` process.run!)))))] ((~' wrap) (~ g!output)))))) (~ g!args)) (#E.Success [(~ g!_) (~ g!output)]) (~ g!output) (#E.Error (~ g!message)) (.error! (~ g!message)) )))) ))) )))