Module Prelude.Macro

Simple macro expansion in strings.

A macro is a pattern in a string that is mapped to a named OCaml function that takes a variable number of parameters and returns some value as its expansion.

A macro call in a string looks like (in the default syntax) "{name}"; this example is nullary (i.e. it takes no parameters). Parameters are separated from the name, and from each other by '|'; here is a macro being called with three parameters: "{name|p1|p2|p3}".

Macro calls nest, so a parameter can be formed from the expansion of some other macro: "A nested call: {foo|My parameter is {bar}.}"

Typcally, macros return string values which simply replace their calls in the string, e.g.:

"I'm not {upcase|yelling}!" 

might become:

"I'm not YELLING!" 

if the implementation of upcase does the obvious thing.

That being said, macros can actually expand to any kind of value, so instead of the simple:

"text {em|contained} in a paragraph" 

expanding to the string:

"text <em>contained</em> in a paragraph" 

the em macro might return a tree structure representing the HTML, as for Caml on the Web:

[`Data "text"; `El ((("", "em"), []), [`Data "contained"]); `Data "in a paragraph"]

A macro also takes an arbitrary data parameter that can be () in the simplest case, or something like an opened database handle or some complex data structure; this value is threaded though all the macro calls and so acts like the accumulator of a fold.

A trivial example of a macro is this one that simply acts as a shorthand for a longer string:

Macro.(define "inria" (fun d _ _ -> d, "Institut national de recherche en informatique et en automatique"))

Note how the macro needs to return the data parameter d.

This can written more compactly, via the Macro.k combinator, as:

Macro.(define "inria" (k "Institut national de recherche en
informatique et en automatique")) 

Here is a macro that does significant work -- file inclusion:

(fun d _ -> function [] -> d,"" | fn::_ -> d,readfile fn) 

Quoting is done with macros that expand to metacharacters; see Macro.left, Macro.sep, and Macro.right for quoters that support the default syntax..

See the macrotutorial.

Exceptions

exception Syntax

Syntax error: unterminated macro call, i.e. missing right.

exception BadName of string

Bad macro name error: currently, a macro name can only be a literal string.

I.e., computed macro names are not (currently) allowed.

exception Undefined of string

Undefined macro: name not in environment.

Types

type ('a, 'b) macro = 'a -> string -> 'b list -> ('a, 'b) result

A macro is a function (fun data name parms -> ...); data is the data parameter passed to eval or to_string; name is the name of the macro (the first argument of define), and parms is a list of the macro's parameters; the macro returns a result.

and ('a, 'b) result = 'a * 'b

The type of macro results, a pair of:

  • the data value (sometimes but not always modified)
  • the value which is the result of its expansion.
and ('a, 'b) env = (string * ('a, 'b) macro) list

The type of environments: your macro definitions are passed to eval or to_string as an alist mapping macro names to their functional values.

type node =
  1. | S of string
  2. | M of string * node list list

The type of parsed macro expression nodes (as returned by parse).

Defining Macros in Environments

val empty : ('a, 'b) env

empty is the empty environment.

val define : string -> ('a, 'b) macro -> ('a, 'b) env -> ('a, 'b) env

(define name macro env) binds name to macro in environment env and returns the new environment.

N.B. the new binding goes at the front of the old environment and hence overrides any older macro of the same name.

Example: if env is the environment:

Macro.(define "a" (k "1") empty |> define "b" (k "2") |> define "a" (k "3"))

then:

(Stream.of_string "{a}" |> Macro.to_string env ()) = "3"

Evaluating Macro Functions (Implementations)

val eval : ?default:('a, 'b) macro -> ('a, 'b) env -> 'a -> string -> 'b list -> ('a, 'b) result

(eval ?default env data name parms) calls the macro bound to name in the environment env with parameters parms, exactly as it would be called if found in the string passed to to_string.

If name is not found in env, default is called (with name as its name). If default is not provided, Undefined is raised.

Example:

  • (eval (define "a" (k "1") empty) () "a" []) = ((), "1")
  • raises Undefined

    if there's no definition for the named macro in env and no default

Syntax

Within a string, a macro call is a parenthesized expresssion (that is, macro calls nest); the opening and closing "parentheses" can be any pair of characters; the defaults are '{' and '}'.

The string of characters beginning after the left paren is the name of the macro; the name is terminated either by the matching right paren, or else by the first parameter separator character, the default being '|'.

Parameters are separated from the name and from each other by this separator.

Here are some calls:

val left : char

The default left parenthesis character ('{').

val sep : char

The default parameter separator character ('|').

val right : char

The default right parenthesis character ('}').

Parsing Strings that Contain Macro Calls

val parse : ?left:char -> ?sep:char -> ?right:char -> char Stream.t -> node list

(parse ?left ?sep ?right stream) parses the string on stream and returns a list of node's.

Example:

(Stream.of_string "Foo {bar|1|2}" |> parse) = [S "Foo "; M ("bar", [[S "1"]; [S "2"]])] 
  • raises Syntax

    upon a syntax error in a macro call

  • raises BadName

    upon an invalid macro name in a call

val defines : ?preserve:bool -> string -> ('a, string) env -> node list -> ('a, string) env * node list

(defines ?preserve definer env nodes) returns the node list nodes after adding in-line macro definitions in nodes to env. This allows the string that's being expanded to define its own macro definitions.

definer is the name of the inline macro-defining macro.

The inline definitions do not have to precede their uses.

Any M-nodes representing calls to definer are removed from the node list, unless (preserve = true).

Example: given the input string:

let str = "Hey, {name}!  Hel{define|name|Buddy}lo." 

we would have:

(Stream.of_string str
|> parse
|> defines "define" []
|> fun (env,ns) -> string_of_nodes env () ns)
= "Hey, Buddy!  Hello." 

Expanding Macros

val fold : ?default:('a, 'b) macro -> ('b list -> string -> 'b list) -> ('b list -> ('a, 'b) result -> 'a * 'b list) -> ('a, 'b) env -> ('a * 'b list) -> node list -> 'a * 'b list

(fold ?default s m env (data,z) nodes) folds s and m over a list of node's, with data as initial macro data parameter and z as the initial accumulator for the fold.

s is applied to the fold accumulator and the string value of each S-node.

m is applied to the fold accumulator and the result of the evaluation of each M-node; the result of the evaluation includes the possibly updated data parameter.

The macro definitions are in the environment env.

data is passed on to each macro call, along with the name the macro was called with, and its list of parameters.

If provided, ~default is a default macro that is called whenever a macro call with an unknown name is encountered. Otherwise, in such a case, Undefined is raised with the name of the offending call.

val string_of_nodes : ?default:('a, string) macro -> ('a, string) env -> 'a -> node list -> string

(string_of_nodes ?default env data nodes) expands the macro calls in nodes and returns macro data and a string representation of the expansion.

It's equivalent to:

fold snoc (fun a (d,s) -> d, s::a) env (data, []) ns |> fun (d,l) -> d, rev l |> String.concat "" 

but more efficient.

val to_string : ?define:'a -> ?left:char -> ?sep:char -> ?right:char -> ?default:('b, string) macro -> ('b, string) env -> 'b -> char Stream.t -> string

(to_string ?define ?left ?sep ?right ?default env data stream) is (parse ~left ~sep ~right stream |> string_of_nodes ?default env data)

  • raises Syntax

    upon a syntax error in a macro call

  • raises BadName

    upon an invalid macro name in a call

Macro Combinators

These may make it easier to write macros.

val k : 'b -> ('a, 'b) macro

(k x) is the constant macro that always expands to x.

val skip1 : (string -> 'b list -> 'b) -> ('a, 'b) macro

(skip1 f) converts function f into a macro by not passing on the data parameter.

f skips the 1st parameter of the function.

val skip2 : ('a -> 'b list -> ('a, 'b) result) -> ('a, 'b) macro

(skip2 f) converts function f into a macro by not passing on the name parameter.

f skips the 2nd parameter of the macro.

val skip12 : ('b list -> 'b) -> ('a, 'b) macro

(skip12 f) converts function f into a macro by passing on neither the data nor name.

f skips the 1st and 2nd parameters of the function.

val syntax : ?def:'b -> int -> ('a -> string -> 'b array -> ('a, 'b) result) -> ('a, 'b) macro

(syntax ?def n f) converts function f into a macro that raises an error if it is not called with exactly n parameters, unless ~def is given, in which case def is returned as the value.

Note that the function f receives its parameters as an array of guaranteed length n, for easier access.

Predefined Macros

module Pre : sig ... end

There is no default env, but you can add any of these to your env.

Tutorial

Coming soon.