Skip to content
Template-based macros for Reason/OCaml
OCaml C++ Shell JavaScript
Branch: master
Clone or download
jaredly Merge pull request #4 from sync/patch-3
Fix ionicon sample code inside readme
Latest commit 41d4e61 Aug 3, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.ci try to get windows working in azure Jul 29, 2019
bs-ppx azure pipelines for building an npm-distributable binary package Jul 29, 2019
esy.lock ok ive got an example of a shared macro. just make a new ppx its fine Jul 28, 2019
example ok ive got an example of a shared macro. just make a new ppx its fine Jul 28, 2019
js ok ive got an example of a shared macro. just make a new ppx its fine Jul 28, 2019
platform title yeah Jul 30, 2019
ppx title yeah Jul 30, 2019
publish-npm version bump Aug 1, 2019
shared-example
src title yeah Jul 30, 2019
test make kitchensink more robust Jul 29, 2019
tests make kitchensink more robust Jul 29, 2019
try-bs try to use the published npm Jul 30, 2019
.gitignore
Readme.md Fix ionicon sample code inside readme Aug 2, 2019
azure-pipelines.yml
bsconfig.json lots more feature complete. now capable of both the monads example an… Jul 25, 2019
dune lockfile Jul 26, 2019
dune-project trying to get dune to let me do this Jul 28, 2019
macros.opam dunify me Jul 26, 2019
pack.sh
package-lock.json
package.json

Readme.md

reason-macros

Template-based macros for Reason/OCaml!

Try it out for yourself

https://astexplorer-macros.surge.sh , switch the language to "Reason" and toggle the "Transform" to on.

Basic examples

let%macro.toplevel ionicon = (name: capIdent, iconName: capIdent) => {
  [%str // toplevel
    module Eval__name = {
      let name = "$eval{name}";
      [@bs.module] [@react.component]
      external make:
        (
          ~className: string=?,
          ~fontSize: string=?,
          ~color: string=?,
          ~onClick: 'event => unit=?
        ) =>
        React.element =
        "react-ionicons/lib/$eval{iconName}";
    }
  ];
};

[%%ionicon (Link, IosLink)];
[%%ionicon (Download, MdDownload)];

becomes

module Link = {
  let name = "Link";
  [@react.component]
  external make:
    (
      ~className: string=?,
      ~fontSize: string=?,
      ~color: string=?,
      ~onClick: 'event => unit=?
    ) =>
    React.element =
    "react-ionicons/lib/IosLink"
};
module Download = {
  let name = "Download";
  [@react.component]
  external make:
    (
      ~className: string=?,
      ~fontSize: string=?,
      ~color: string=?,
      ~onClick: 'event => unit=?
    ) =>
    React.element =
    "react-ionicons/lib/MdDownload"
};
let%macro.let opt = (pattern, value, continuation) =>
  switch (eval__value) {
  | None => None
  | Some(eval__pattern) => eval__continuation
  };

let doSomethingWithOptionals = () => {
  let%opt who = Some("world");
  let%opt greeting = Some("Hello");
  Some(greeting ++ "" ++ who);
};

becomes

let doSomethingWithOptionals = () =>
  switch (Some("world")) {
  | None => None
  | Some(who) =>
    switch (Some("Hello")) {
    | None => None
    | Some(greeting) => Some(greeting ++ "" ++ who)
    }
  };
let%macro.let async = (pattern, value, continuation) => {
  Js.Promise.then_(
    eval__pattern => eval__continuation,
    eval__value
  );
};

let doSomethingAsync = () => {
  let%async text = fetch("Hello");
  let%async more = text->json;
  Js.Promise.resolve(more);
};

becomes

let doSomethingAsync = () =>
  Js.Promise.then_(
    text => Js.Promise.then_(more => Js.Promise.resolve(more), json(text)),
    fetch("Hello"),
  );

Installation

Bucklescript

npm i reason-macros-bin

  "ppx-flags": ["reason-macros-bin/ppx.js"]

esy + dune

add to package.json / esy.json "reason-macros": "git+https://github.com/jaredly/reason-macros"

and then in a dune file

  (preprocess (pps macros.ppx))

!IMPORTANT! The rest of this Readme is very disorganized and wrong.

User-defined macros. Without ppxs.

let%macro.fn something = (one, two, three) => {
  one + two + three
};

[%macro something(5, 6, 7)]

// I would like to support spreads (rest arguments).

// Also, macro-ify a record literal
// Or a js object literal

// Allow special things like [%FILENAME] and [%LINENO] and stuff
// Also would like a way to include a representation of the expression as text or something.

let%macro.switch thing = (value, cases) => {
  // hrmmm
  // is there a way to allow pseudo-procedures?
  // not suer.
  // Like going through each case and doing something to it.
  [%map cases(({pattern, condition, body}) => {
    [%case (pattern, condition, body)]
  })]
}


let%macro.let opt = (pattern, value, continuation) => {
  switch value {
    | Some(pattern) => continuation
    | None => None
  }
};

let%macro.let async = (pattern, value, continuation) => {
  Js.Promise.then_(value, pattern => continuation)
};

let%macro.async awesome = 10;

  • Simple macros
    • List of magics
      • for all macros

      • for decorator macros

        • payload is the thing we're attached to. Will have to get fancy for local bindings, how to include just the binding and not the continuation

        • for a binding

          • payload.binding.name will give you the name as an identifier token

            • will throw exception if it's attached to not-a-binding
          • payload.binding.value will give you the stuff to the right of the equals

          • payload.binding.continuation will give you the stuff after the binding, if we're in an expression.

          • payload.binding.value.args will give you the args? if the value is a function. not sure how you would manipulate the args

        • for a type

          • payload.type.name the name

          • payload.type.manifest the manifest

          • not sure what you can do with it.

[%macro.decorator.withList [%payload]; type arrayThing = array([%payload.type.name]) ]


  - Things to figure out
    - how to determine whether to replace the target (when it's a decorator) or not? maybe always replace. So will have to specify that it's a decorator.
      - macro.decorator

    - Do I want to be able to do any logic? maybe with like an `[%conditional if ([%arg "name"] == "awesome") { ... } else { ... } ]`. Yeah! call it `@eval` or `@preval`
      - would have a fairly limited set of comparisons you could do.

      - Exercise: can I reproduce conditional compilation? I'll want a way to rect to ENV vars
        - ```
[%macro.decorator.native
  if%eval ([%env "bsb-backend"] == "native") { [%payload] }
];
[%macro.decorator.js
  if%eval ([%env "bsb-backend"] == "js") { [%payload] }
];
 
[@native]
let platform = "native";
 
[@js]
let platform = "js";
  - What about a switch on multiple backends? like Platform.select
    - ```

[%macro.platform switch%eval ([%env "bsb-backend"]) { | "native" => [%arg "native"] | "js" => [%arg "js"] } ];

Examples of things I want to be able to do:

A platform macro

let%macro platform = (record: record) => switch%eval ([%env "bsb-backend"]) {
  | "native" => record.native
  | "js" => record.js
}

[%platform {
  | "native" => 10
  | "js" => 5
}]

----

let%macro.decorator js = (payload: expression) => if%eval ([%env "bsb-backend"] == "js") { payload } else {()};
let%macro.decorator.str js = (payload: structure_item) => if%eval ([%env "bsb-backend"] == "js") { payload } else {()};

let_ppx let%Anything style

let%macro.let async = (pattern, value, continuation) => {
  Js.Promise.then_(value, pattern => continuation)
}

let%async {something} = aPromise;
Js.Promise.resolve(ok)

->

Js.Promise.then_(aPromise, ({something}) => Js.Promise.resolve(ok))
let%macro.try async = (value, cases: rest(case)) => {
  Js.Promise.catch(value, err => [%construct.switch (err, cases)])
}

let caught = try%async (bPromise) {
  | "someText" => Js.Promise.resolve(ok)
  | exn => Js.Promise.reject(exn)
};

->

Js.Promise.catch(bPromise, err => switch err {
  | "someText" => Js.Promise.resolve(ok)
  | exn => Js.Promise.reject(exn)
})

(nope) A "super assert"

[%super_assert a == 5]

-> if (!(a == b)) {
  print_endline("a == 5")
  assert(false)
}

or maybe

[%assert_equal (a, b)]

->
if (a != b) {
  print_endline(ermmm ok I need type info here, darn)
}

Basic form

MacroTypes:

  • pattern (generic pattern, can do a switch on it)

  • expression (generic epxression, can switch probably)

  • ident - if this is an argument that is already a pattern, then we parse a pattern ident. Otherwise an expression ident.

  • (tuple literal) translates into the corresponding tuple form, for pattern or expr

  • record - some kind of record literal. attributes can be gotten out, and will be checked at macro evaluation time. Can also get the fields as array((ident, expr)), and the rest arg maybe?

  • js_object - a js object literal

  • string (string literal)

  • int (int literal)

  • float (float literal)

  • poly_variant - &backtick;Awesome

    • dunno about args. Might be nice to switch on the arg
    • but I guess I can cover that with - the type is an expression, and I do a switch on it.
    • yeah so maybe I don't need to be able to specify poly_variant? Unless I want to use the string of the variant name somehow. could be a future task

YOOO if I make this, I totally need an online playground for working with it. Must make jsoo compatible if at all possible.

Forms:

  • %macro - a basic macro, multiple arguments given as a tuple literal. non-expression arguments given via [%t: ] and [%p? ]
    • rest argument as the last one, others: rest(expression)
  • %macro.let has two forms:
    • let%macro.let a = (pattern: pattern, typ: type, expr: expression, expr: expression) =>
      • maybe cann this macro.let.typed?
    • let%macro.let a = (pattern: pattern, expr: expression, expr: expression) =>
  • %macro.let.toplevel
  • %macro.decorator
  • %macro.decorator.str
  • %macro.decorator.pat
  • %macro.decorator.typ
  • %macro.switch
    • let%macro.switch a = (expr: expression, (pattern, cexpr): case, cases: rest(case)) =>
    • not sure what can be done with this... other than constructing a new switch, I guess maybe with the pattern altered or something
    • [%construct.switch (expr, cases.map(((pattern, cexpr)) => ([%p? Some(pattern)], cexpr)))]
  • %macro.try
    • let%macro.try a = (expr: expression, cases: rest(case)) => Js.Promise.catch(expr, err => [%construct.switch (err, cases)])

Eval functions:

  • [%eval switch_(expr, cases)]

  • [%eval map(value, item => { /* treated the same as a macro body */ })] // if the body is a [%str], then will make multiple strs. Otherwise, will make a list literal probably?

  • [%eval foreach(value, item => {})] // if in a structure context, the body must be %str. Otherwise, will be exp_sequence'd together

  • let%eval something = otherthing // if it's a constant, will be processed as such. otherwise, will be a plain expr

  • if%eval (x == 2) {}

  • for%eval (x in 0 to 5) {} // if the body is a [%str], then it will make structured items. otherwise, will be exp_sequenced together.

  • [%construct.switch (expr, cases)] cases must be a list(case), expr must be some kind of expression

  • [%construct.let (pattern, expr, continuation)]

  • [%construct.let.typed (pattern, typ, expr, continuation)]

  • [%error "Some macro error message"]

    • would be nice to be able to provide an ast node to attach it to
  • [%construct.attribute (expr, string or ident)]

  • [%construct.js_attribute (expr, string or ident)]

  • [%env "some string"] get an environmental variable at compile-time, you can do logic with it, becomes a string literal

Transformations:

  • [%string! some_ast_node] - pretty prints it out using refmt
  • somearray.map(fn => thing) - transform an array of something into something else....
  • somearray.reduce(base, (collector, item) => collector) - reduce. not sure how far I can go with this
    • UPDATE: maybe get_in isn't even interesting
    • would be nice to make it so I can support get_in(some, ["a", "b", "c"])
    • even fancier get_in(some, ["a", &backquot;opt("b"), "c"])
let%macro get_in = (target: expression, path: list(string)) => {
  path.reduce(target, (target, item) => [%construct.attribute (target, item)])
}

// dunno if I can get this done tbh
let%macro get_in = (target: expression, path: list(expression)) => {
  path.reduce(target, (target, item) => switch%eval item {
    | `opt(expr) => switch ([%construct.]) {},
  });

  // yeah way too complicated
  loop((target, items), (target, items) => {
    switch%eval items {
      | [] => target
      | [one, ...rest] => [%recur (target, rest)]
    }
  })
}
let%macro name = (arg1: type1, arg2: type2) => {
  body
}

[%name (arg1, arg2)]

let%macro.let name = (pattern: pattern, expr: expression, continuation: expression) => {

}

let%macro name = (arg: array(string)) => {
  arg.map(item => [%eval.concat (item, "hello")])
}





->> instead of `arg`
I think I'd rather have it be
let%macro.record platform = record => {
  switch%eval ([%env "bsb-backend"]) {
    | "native" => record.native
    | "js" => record.js
  }
}
 
let x = [%platform {
  native: "someNative",
  js: "someJs"
}];
  - How to do arguments?
    - magically interpolate a record definition into arguments
      - so like
let x = [%platform {
  native: "someNative",
  js: "someJs",
}]
    - use `[@arg.somearg "contents"]` decorators
      - 
let x = [@arg.native "someNative"]
[@arg.js "someJs"]
[%platform]
[@t {
  name: "contents",
  otherThing: 10,
}]
//
[%macro.t
  Testing.run([%arg "name"], [%arg "otherThing"])
]

Some rust macros

https://doc.rust-lang.org/1.7.0/book/macros.html


#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}


macro_rules! write_html {
    ($w:expr, ) => (());

    ($w:expr, $e:tt) => (write!($w, "{}", $e));

    ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{
        write!($w, "<{}>", stringify!($tag));
        write_html!($w, $($inner)*);
        write!($w, "</{}>", stringify!($tag));
        write_html!($w, $($rest)*);
    }};
}

fn main() {
    use std::fmt::Write;
    let mut out = String::new();

    write_html!(&mut out,
        html[
            head[title["Macros guide"]]
            body[h1["Macros are the best!"]]
        ]);

    assert_eq!(out,
        "<html><head><title>Macros guide</title></head>\
         <body><h1>Macros are the best!</h1></body></html>");
}


You can’t perform that action at this time.