diff options
Diffstat (limited to 'stdlib/source/lux/macro/syntax.lux')
-rw-r--r-- | stdlib/source/lux/macro/syntax.lux | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/stdlib/source/lux/macro/syntax.lux b/stdlib/source/lux/macro/syntax.lux new file mode 100644 index 000000000..917b7e094 --- /dev/null +++ b/stdlib/source/lux/macro/syntax.lux @@ -0,0 +1,297 @@ +(;module: + lux + (lux [macro #+ with-gensyms] + (control [monad #+ do Monad] + [eq #+ Eq] + ["p" parser]) + (data [bool] + [number] + [text "text/" Monoid<Text>] + [ident] + (coll [list "list/" Functor<List>]) + [product] + [maybe] + ["E" error])) + (.. [code "code/" Eq<Code>])) + +## [Utils] +(def: (join-pairs pairs) + (All [a] (-> (List [a a]) (List a))) + (case pairs + #;Nil #;Nil + (#;Cons [[x y] pairs']) (list& x y (join-pairs pairs')))) + +## [Types] +(type: #export Syntax + {#;doc "A Lux syntax parser."} + (p;Parser (List Code))) + +## [Utils] +(def: (remaining-inputs asts) + (-> (List Code) Text) + ($_ text/compose "\nRemaining input: " + (|> asts (list/map code;to-text) (list;interpose " ") (text;join-with "")))) + +## [Syntaxs] +(def: #export any + {#;doc "Just returns the next input without applying any logic."} + (Syntax Code) + (function [tokens] + (case tokens + #;Nil (#E;Error "There are no tokens to parse!") + (#;Cons [t tokens']) (#E;Success [tokens' t])))) + +(do-template [<get-name> <type> <tag> <eq> <desc>] + [(def: #export <get-name> + {#;doc (code;text ($_ text/compose "Parses the next " <desc> " input Code."))} + (Syntax <type>) + (function [tokens] + (case tokens + (#;Cons [[_ (<tag> x)] tokens']) + (#E;Success [tokens' x]) + + _ + (#E;Error ($_ text/compose "Cannot parse " <desc> (remaining-inputs tokens))))))] + + [ bool Bool #;Bool bool;Eq<Bool> "bool"] + [ nat Nat #;Nat number;Eq<Nat> "nat"] + [ int Int #;Int number;Eq<Int> "int"] + [ deg Deg #;Deg number;Eq<Deg> "deg"] + [ frac Frac #;Frac number;Eq<Frac> "frac"] + [ text Text #;Text text;Eq<Text> "text"] + [symbol Ident #;Symbol ident;Eq<Ident> "symbol"] + [ tag Ident #;Tag ident;Eq<Ident> "tag"] + ) + +(def: #export (this? ast) + {#;doc "Asks if the given Code is the next input."} + (-> Code (Syntax Bool)) + (function [tokens] + (case tokens + (#;Cons [token tokens']) + (let [is-it? (code/= ast token) + remaining (if is-it? + tokens' + tokens)] + (#E;Success [remaining is-it?])) + + _ + (#E;Success [tokens false])))) + +(def: #export (this ast) + {#;doc "Ensures the given Code is the next input."} + (-> Code (Syntax Unit)) + (function [tokens] + (case tokens + (#;Cons [token tokens']) + (if (code/= ast token) + (#E;Success [tokens' []]) + (#E;Error ($_ text/compose "Expected a " (code;to-text ast) " but instead got " (code;to-text token) + (remaining-inputs tokens)))) + + _ + (#E;Error "There are no tokens to parse!")))) + +(do-template [<name> <comp> <error>] + [(def: #export <name> + (Syntax Int) + (do p;Monad<Parser> + [n int + _ (p;assert <error> (<comp> 0 n))] + (wrap n)))] + + [pos-int i.> "Expected a positive integer: N > 0"] + [neg-int i.< "Expected a negative integer: N < 0"] + ) + +(do-template [<name> <tag> <desc>] + [(def: #export <name> + {#;doc (code;text ($_ text/compose "Parse a local " <desc> " (a " <desc> " that has no module prefix)."))} + (Syntax Text) + (function [tokens] + (case tokens + (#;Cons [[_ (<tag> ["" x])] tokens']) + (#E;Success [tokens' x]) + + _ + (#E;Error ($_ text/compose "Cannot parse local " <desc> (remaining-inputs tokens))))))] + + [local-symbol #;Symbol "symbol"] + [ local-tag #;Tag "tag"] + ) + +(do-template [<name> <tag> <desc>] + [(def: #export (<name> p) + {#;doc (code;text ($_ text/compose "Parse inside the contents of a " <desc> " as if they were the input Codes."))} + (All [a] + (-> (Syntax a) (Syntax a))) + (function [tokens] + (case tokens + (#;Cons [[_ (<tag> members)] tokens']) + (case (p members) + (#E;Success [#;Nil x]) (#E;Success [tokens' x]) + _ (#E;Error ($_ text/compose "Syntax was expected to fully consume " <desc> (remaining-inputs tokens)))) + + _ + (#E;Error ($_ text/compose "Cannot parse " <desc> (remaining-inputs tokens))))))] + + [ form #;Form "form"] + [tuple #;Tuple "tuple"] + ) + +(def: #export (record p) + {#;doc (code;text ($_ text/compose "Parse inside the contents of a record as if they were the input Codes."))} + (All [a] + (-> (Syntax a) (Syntax a))) + (function [tokens] + (case tokens + (#;Cons [[_ (#;Record pairs)] tokens']) + (case (p (join-pairs pairs)) + (#E;Success [#;Nil x]) (#E;Success [tokens' x]) + _ (#E;Error ($_ text/compose "Syntax was expected to fully consume record" (remaining-inputs tokens)))) + + _ + (#E;Error ($_ text/compose "Cannot parse record" (remaining-inputs tokens)))))) + +(def: #export end! + {#;doc "Ensures there are no more inputs."} + (Syntax Unit) + (function [tokens] + (case tokens + #;Nil (#E;Success [tokens []]) + _ (#E;Error ($_ text/compose "Expected list of tokens to be empty!" (remaining-inputs tokens)))))) + +(def: #export end? + {#;doc "Checks whether there are no more inputs."} + (Syntax Bool) + (function [tokens] + (case tokens + #;Nil (#E;Success [tokens true]) + _ (#E;Success [tokens false])))) + +(def: #export (on compiler action) + {#;doc "Run a Lux operation as if it was a Syntax parser."} + (All [a] (-> Compiler (Meta a) (Syntax a))) + (function [input] + (case (macro;run compiler action) + (#E;Error error) + (#E;Error error) + + (#E;Success value) + (#E;Success [input value]) + ))) + +(def: #export (run inputs syntax) + (All [a] (-> (List Code) (Syntax a) (E;Error a))) + (case (syntax inputs) + (#E;Error error) + (#E;Error error) + + (#E;Success [unconsumed value]) + (case unconsumed + #;Nil + (#E;Success value) + + _ + (#E;Error (text/compose "Unconsumed inputs: " + (|> (list/map code;to-text unconsumed) + (text;join-with ", "))))))) + +(def: #export (local inputs syntax) + {#;doc "Run a syntax parser with the given list of inputs, instead of the real ones."} + (All [a] (-> (List Code) (Syntax a) (Syntax a))) + (function [real] + (do E;Monad<Error> + [value (run inputs syntax)] + (wrap [real value])))) + +## [Syntax] +(def: #hidden text.join-with text;join-with) + +(def: #hidden _run_ p;run) +(def: #hidden _Monad<Parser>_ p;Monad<Parser>) + +(macro: #export (syntax: tokens) + {#;doc (doc "A more advanced way to define macros than macro:." + "The inputs to the macro can be parsed in complex ways through the use of syntax parsers." + "The macro body is also (implicitly) run in the Monad<Meta>, to save some typing." + "Also, the compiler state can be accessed through the *compiler* binding." + (syntax: #export (object [#let [imports (class-imports *compiler*)]] + [#let [class-vars (list)]] + [super (opt (super-class-decl^ imports class-vars))] + [interfaces (tuple (some (super-class-decl^ imports class-vars)))] + [constructor-args (constructor-args^ imports class-vars)] + [methods (some (overriden-method-def^ imports))]) + (let [def-code ($_ text/compose "anon-class:" + (spaced (list (super-class-decl$ (maybe;default object-super-class super)) + (with-brackets (spaced (list/map super-class-decl$ interfaces))) + (with-brackets (spaced (list/map constructor-arg$ constructor-args))) + (with-brackets (spaced (list/map (method-def$ id) methods))))))] + (wrap (list (` ((~ (code;text def-code)))))))))} + (let [[exported? tokens] (case tokens + (^ (list& [_ (#;Tag ["" "hidden"])] tokens')) + [(#;Some #;Left) tokens'] + + (^ (list& [_ (#;Tag ["" "export"])] tokens')) + [(#;Some #;Right) tokens'] + + _ + [#;None tokens]) + ?parts (: (Maybe [Text (List Code) Code Code]) + (case tokens + (^ (list [_ (#;Form (list& [_ (#;Symbol ["" name])] args))] + body)) + (#;Some name args (` {}) body) + + (^ (list [_ (#;Form (list& [_ (#;Symbol ["" name])] args))] + meta-data + body)) + (#;Some name args meta-data body) + + _ + #;None))] + (case ?parts + (#;Some [name args meta body]) + (with-gensyms [g!tokens g!body g!msg] + (do macro;Monad<Meta> + [vars+parsers (monad;map @ + (: (-> Code (Meta [Code Code])) + (function [arg] + (case arg + (^ [_ (#;Tuple (list var parser))]) + (wrap [var parser]) + + [_ (#;Symbol var-name)] + (wrap [(code;symbol var-name) (` any)]) + + _ + (macro;fail "Syntax pattern expects tuples or symbols.")))) + args) + #let [g!state (code;symbol ["" "*compiler*"]) + error-msg (code;text (text/compose "Wrong syntax for " name)) + export-ast (: (List Code) (case exported? + (#;Some #E;Error) + (list (' #hidden)) + + (#;Some #E;Success) + (list (' #export)) + + _ + (list)))]] + (wrap (list (` (macro: (~@ export-ast) ((~ (code;symbol ["" name])) (~ g!tokens) (~ g!state)) + (~ meta) + ("lux case" (;;run (~ g!tokens) + (: (Syntax (Meta (List Code))) + (do ;;_Monad<Parser>_ + [(~@ (join-pairs vars+parsers))] + ((~' wrap) (do macro;Monad<Meta> + [] + (~ body)))))) + {(#E;Success (~ g!body)) + ((~ g!body) (~ g!state)) + + (#E;Error (~ g!msg)) + (#E;Error (text.join-with ": " (list (~ error-msg) (~ g!msg))))}))))))) + + _ + (macro;fail "Wrong syntax for syntax:")))) |