Syntax and semantics
The core metalanguage is fairly small. It has only these four types of expressions:
ML99_call(F, ...)
invokesF
with provided arguments.F
must be either a functional macro identifier or an expression that evaluates to a functional macro identifier;...
must comprise a non-empty sequence of comma-separated expressions.v(...)
merely evaluates to its arguments, e.g.v(123)
evaluates to123
.ML99_abort(...)
evaluates a non-empty sequence of comma-separated expressions and immediately aborts interpretation.ML99_fatal(F, ...)
aborts interpretation with an appropriate error message.
(Indeed, expressions do nothing unless interpreted -- i.e., they are lazy.)
All metaprograms in Metalang99 represent a sequence of these syntactic forms. The interpreter itself is called via ML99_EVAL(...)
.
Let's move on to examples. Consider this:
It simply evaluates to 123 ~ ***
as expected. Now consider the mechanics of a functional macro (aka "metafunction"):
It evaluates to 1 + 2 + 3
. Here, v(1), v(2), v(3)
is a sequence of comma-separated expressions provided to F
: Metalang99 evaluates each one and applies them to F
like this: F_IMPL(1, 2, 3)
. Notice that if we write ML99_call(F, v(1, 2, 3))
, we achieve the same result because v(1, 2, 3)
evaluates to 1, 2, 3
-- exactly the same arguments.
As you can see, the syntax ML99_call(F, ...)
is a bit inconvenient. For the sake of proper code formatting and IDE support, the convention used by the Metalang99 standard library is to define a wrapper macro that expands to a Metalang99 call:
This way FOO
can be conveniently called as FOO(v(1), v(2), v(3))
.
Recursion
Why do we need custom syntax for invoking functional macros? Because it lets you express recursion with no hassle! Consider the following demonstrative example:
It evaluates to 123
, as expected. However, without Metalang99, the expansion gets blocked:
It happens because X(CALL_X)
expands to CALL_X(123)
, which, in turn, expands to X(ID)
-- the recursive macro call which is blocked by the preprocessor (see the Cloak wiki).
General macro recursion allows expressing things that were inexpressible using vanilla preprocessor macros. For example, you can leverage Cons-lists to do pretty much anything with unbounded sequences of arguments, as we shall see later. Internally, such metafunctions as ML99_listReplicate
, ML99_listReverse
, or ML99_listFilter
are implemented by structural recursion, which would be impossible without Metalang99.
Appendix: The use of `v`
Throughout the examples, you might have noticed the extensive use of the v(...)
expression. Although it may be a bit confusing for newcomers, the purpose of v(...)
is pretty simple: just say the Metalang99 interpreter to evaluate your stuff inside the parentheses as-is.
Take a look at this erroneous metafunction:
If we try to use it like this: ML99_EVAL(ML99_call(POW2, v(3)))
, Metalang99 would complain at us:
The reason for this is that every single metafunction called by ML99_call
(ML99_callUneval
, ML99_appl
) must emit a proper Metalang99 term; 3 * 3
is not a proper term according to the core language syntax. To fix the problem, just wrap x * x
into v
:
You can think of v
as of a literal expression, like "abc"
or 42
in most programming languages. However, if you want Metalang99 to evaluate an expression according to its semantics, you need not use v(...)
but instead provide a computable term:
The same holds for metafunction arguments.
Last updated