The obvious thing to do with a formula is to evaluate it. The following `proc`

does just that. It requires a mapping `varToVal`

from the variable name to its value:

from math import pow proc evaluate(n: Formula; varToVal: proc (name: string): float): float = case n.kind of fkVar: varToVal(n.name) of fkLit: n.value of fkAdd: evaluate(n.left, varToVal) + evaluate(n.right, varToVal) of fkMul: evaluate(n.left, varToVal) * evaluate(n.right, varToVal) of fkExp: pow(evaluate(n.left, varToVal), evaluate(n.right, varToVal))

Now, to check whether a formula is a polynomial (to see if we can easily differentiate it, for instance), we can use the following code:

proc isPolyTerm(n: Formula): bool = n.kind == fkMul and n.left.kind == fkLit and (let e = n.right; e.kind == fkExp and e.left.kind == fkVar and e.right.kind == fkLit) proc isPolynomial(n: Formula): bool = isPolyTerm(n) or (n.kind == fkAdd and isPolynomial(n.left) and isPolynomial(n.right))

`isPolyTerm`

is quite ugly. Pattern matching would be much nicer. While Nimrod does not support elaborate pattern matching beyond case out-of-the-box, it's quite easy to implement it thanks to the sophisticated macro system: For pattern matching, we define a macro `=~`

that constructs the `and`

expression at compile time. Then the code can look like this:

proc isPolyTerm(n: Formula): bool = n =~ fkMul(fkLit, fkExp(fkVar, fkLit))

But this is still not as nice as it could be: The point of Nimrod's macros is that they enable DSLs that make use of Nimrod's lovely infix syntax. In fact, that's a conscious design decision: Macros do not affect Nimrod's syntax — they can only affect the semantics. This helps readability.

So here is what we really want to support:

proc isPolyTerm(n: Formula): bool = n =~ c * x^c

Where `c`

matches any literal , `x`

matches any variable, and the operators their corresponding formula kinds:

proc pat2kind(pattern: string): FormulaKind = case pattern of "^": fkExp of "*": fkMul of "+": fkAdd of "x": fkVar of "c": fkLit else: fkVar # no error reporting for reasons of simplicity

Note that for reasons of simplicity, we don't implement any kind of variable binding, so `1 * x^2`

matches `c * x^c `

as `c`

is not bound to the literal `1`

in any way. This form of variable binding is called *unification*. Without unification, the pattern matching support is still quite primitive. However, unification requires a notion of equality, and since many useful but different equality relations exist, pattern matching is not baked into the language.

So here's the implementation of the `=~`

macro in all its glory:

import macros proc matchAgainst(n, pattern: PNimrodNode): PNimrodNode {.compileTime.} = template `@`(current, field: expr): expr = newDotExpr(current, newIdentNode(astToStr(field))) template `==@`(n, pattern: expr): expr = newCall("==", n@kind, newIdentNode($pat2kind($pattern.ident))) case pattern.kind of CallNodes: result = newCall("and", n ==@ pattern[0], matchAgainst(n@left, pattern[1])) if pattern.len == 3: result = newCall("and", result.copy, matchAgainst(n@right, pattern[2])) of nnkIdent: result = n ==@ pattern of nnkPar: result = matchAgainst(n, pattern[0]) else: error "invalid pattern" macro `=~` (n: Formula; pattern: expr): bool = result = matchAgainst(n, pattern)

In Nimrod, a template is a declarative form of a macro, while a `macro`

is imperative. It constructs the AST with the help of an API that can be found in the macros module, so that's what line 1 imports. The final macro definition is in line 25 and it follows a fairly common approach: It delegates all of its work to a helper `proc`

called `matchAgainst`

, which constructs the resulting AST recursively. `PNimrodNode`

is the type the Nimrod AST consists of. The Nimrod AST is structured quite similar to how we implemented `Formula`

, except that every node can have a variable number of children. `n[i]`

is the `i`

th child.

The various function application syntaxes (prefix, infix, command) all map to the same AST structure` kind(callee, arg1, arg2, ...)`

, where `kind`

describes the particular syntax. In `matchAgainst`

, we treat every call syntax the same with the help of `macros.CallNodes`

. We allow for `a(b)`

and `a(b, c)`

(line 15) call syntaxes and construct the AST representing an `and`

expression with the help of two` @`

and `==@`

templates.

`n@field`

constructs the AST that corresponds to `n.field`

and `a ==@ b`

constructs` a.kind == pat2kind(b)`

. Line 18 deals with the case when the pattern only consists of a single identifier (`nnkIdent`

), and line 20 supports `()`

(`nnkPar`

) so that grouping in a pattern is allowed.

As this example shows, metaprogramming is a good way to transform two lines of long ugly code to a short beautiful one-liner at the cost of 30 lines of ugly code dealing with AST transformations. However, the DSL we created here pays off as soon as there are more patterns to match against. It's also reasonably easy to abstract the `=~`

pattern-match operator so that it operates on more than just the `Formula`

data type. In fact, a library solution that also supports unification is in development.

### Conclusion

Nimrod is open source software that runs on Windows, Linux, Mac OS, and BSD. In addition to generating C and JavaScript, it can generate C++ or Objective-C. The compiler can optionally enforce all kinds of error checking (bounds checking, overflow, etc.) and it can perform extensive optimizations.

It has an extensive standard library and many ported libraries. In addition, it has wrappers for most of the premier C libraries (including OLE, X, Glib/GTK, SQLite, etc.) and C-based languages (Lua and Tcl). If you're searching for a systems programming language that provides higher-level constructs and extensive metaprogramming, but boils down to C, Nimrod might well be what you're looking for.

*Andreas Rumpf is the creator of Nimrod. *