Quantcast
Channel: Hacker News
Viewing all articles
Browse latest Browse all 737

Nimrod: A new approach to metaprogramming

$
0
0
A new approach to meta programming

Nimrod logo

  1. What is Nimrod?
  2. Implementation aspects
  3. "Hello World"
  4. Meta programming features
  5. Optimizing "Hello World" via term rewriting macros
  6. Hoisting via term rewriting macros
  7. Summary of Nimrod's meta programming features
varinput:TaintedString
  • systems programming language
vara=cast[int](gch.stackBottom)
varinput:TaintedString
  • systems programming language
vara=cast[int](gch.stackBottom)
iteratorfrom1to2():int=yield1;yield2
varinput:TaintedString
  • systems programming language
vara=cast[int](gch.stackBottom)
iteratorfrom1to2():int=yield1;yield2
  • and strong meta programming capabilities.
template`!=`(x,y:expr):expr=not(x==y)
  • Nimrod compiles to C; C++ and Objective-C are also supported
  • it compiles to JavaScript
  • it provides a realtime GC which supports max pause times of 1-2 miliseconds which means it is perfectly useful even for demanding games
  • the Nimrod compiler and all of the standard library (including the GC) are written in Nimrod
  • whole program dead code elimination: stdlib carefully crafted to make use of it; for instance parsers do not use (runtime) regular expression -> re engine not part of the executable
  • the GC is also optimized away if you do not use it
  • our infrastructure (IDE, build farm, build tools, package manager) is also completely written in Nimrod

Aporia Screenshot

echo"hello ","world",99
echo"hello ","world",99

is rewritten to:

echo([$"hello ",$"world",$99])
  • echo is declared as: proc echo(a: varargs[string, `$`]); $ (Nimrod's toString operator) is applied to every argument
  • local type converter: only in this context everything is converted to a string via $
  • in contrast to C#/Java's solution this requires no dynamic binding
echo"hello ","world",99

is rewritten to:

echo([$"hello ",$"world",$99])
  • echo is declared as: proc echo(a: varargs[string, `$`]); $ (Nimrod's toString operator) is applied to every argument
  • local type converter: only in this context everything is converted to a string via $
  • in contrast to C#/Java's solution this requires no dynamic binding
  • it is extensible:
proc`$`(x:MyObject):string=x.svarobj=MyObject(s:"xyz")echoobj

Nimrod's focus is meta programming; macros are used

1. to avoid code duplication / boilerplate:

01templatehtmlTag(tag:expr){.immediate.}=02proctag():string="<"&astToStr(tag)&">"0304htmlTag(br)05htmlTag(html)0607echobr()

Produces:

<br>

2. for control flow abstraction:

01templateonce(body:stmt)=02varx{.global.}=false03ifnotx:04x=true05body0607procp()=08once:09echo"first call of p"10echo"some call of p"1112p()13once:14echo"new instantiation"15p()

2. for control flow abstraction:

01templateonce(body:stmt)=02varx{.global.}=false03ifnotx:04x=true05body0607procp()=08once:09echo"first call of p"10echo"some call of p"1112p()13once:14echo"new instantiation"15p()

Produces:

first call of p
some call of p
new instantiation
some call of p

3. for lazy evaluation:

01templatelog(msg:string)=02ifdebug:03echomsg0405log("x: "&$x&", y: "&$y)

4. to implement DSLs:

01htmlmainPage:02head:03title"now look at this"04body:05ul:06li"Nimrod is quite capable"0708echomainPage()

Produces:

<html>
  <head><title>now look at this</title></head>
  <body>
    <ul>
      <li>Nimrod is quite capable</li>
    </ul>
  </body>
</html>

Implementation:

01templatehtml(name:expr,matter:stmt){.immediate.}=02procname():string=03result="<html>"04matter05result.add("</html>")0607templatenestedTag(tag:expr){.immediate.}=08templatetag(matter:stmt){.immediate.}=09result.add("<"&astToStr(tag)&">")10matter11result.add("</"&astToStr(tag)&">")1213templatesimpleTag(tag:expr){.immediate.}=14templatetag(matter:expr){.immediate.}=15result.add("<$1>$2</$1>"%[astToStr(tag),matter])1617nestedTagbody18nestedTaghead19nestedTagul20simpleTagtitle21simpleTagli

After macro expansion:

templatehtml(name:expr,matter:stmt){.immediate.}=procname():string=result="<html>"matterresult.add("</html>")templatehead(matter:stmt){.immediate.}=result.add("<"&astToStr(head)&">")matterresult.add("</"&astToStr(head)&">")...templatetitle(matter:expr){.immediate.}=result.add("<$1>$2</$1>"%[astToStr(title),matter])templateli(matter:expr){.immediate.}=result.add("<$1>$2</$1>"%[astToStr(li),matter])
01htmlmainPage:02head:03title"now look at this"04body:05ul:06li"Nimrod is quite capable"0708echomainPage()

Is translated into:

procmainPage():string=result="<html>"result.add("<head>")result.add("<$1>$2</$1>"%["title","now look at this"])result.add("</head>")result.add("<body>")result.add("<ul>")result.add("<$1>$2</$1>"%["li","Nimrod is quite capable"])result.add("</ul>")result.add("</body>")result.add("</html>")

Compile time function evaluation optimizes 'mainPage()' into:

"<html><head><title>now look at this</title></head><body>..."

5. to provide user defined optimizations (via term rewriting macros).

01type02MyObject=object03a,b:int04s:string05letobj=MyObject(a:3,b:4,s:"abc")06echoobj

Produces (roughly):

(a:3b:4s:"abc")

How does it work?

  • $ for object is in Nimrod's system module (like Haskell's prelude)
0102proc`$`[T:object](x:T):string=03result="("04forname,valueinfieldPairs(x):05result.add("$1: $2\n"%[name,$value])06result.add(")")

Notes:

  • [T: object] -- generic type + constraint
  • relies on builtin fieldPairs iterator
  • Builtin fieldPairs iterator transforms the loop into the following:
result="("result.add("$1: $2\n"%["a",$obj.a])result.add("$1: $2\n"%["b",$obj.b])result.add("$1: $2\n"%["s",$obj.s])result.add(")")
  • Builtin fieldPairs iterator transforms the loop into the following:
result="("result.add("$1: $2\n"%["a",$obj.a])result.add("$1: $2\n"%["b",$obj.b])result.add("$1: $2\n"%["s",$obj.s])result.add(")")

Desired result:

result=`&`("(a: ",$obj.a,"\nb: ",$obj.b,"\ns: ",$obj.s,"\n)")

We need partial evaluation for '%'.

%'s implementation (simplified):

01proc`%`(f:string,a:openArray[string]):string=02result=""03vari=004whilei<f.len:05iff[i]=='$':06casef[i+1]07of'1'..'9':08varj=009i+=110whilef[i]in{'0'..'9'}:11j=j*10+ord(f[i])-ord('0');i+=112result.add(a[j-1])13else:14invalidFormatString()15else:16result.add(f[i]);i+=1
01macrooptFormat{`%`(f,a)}(f:string{lit},a:openArray[string]):expr=02result=newCall("&")03vari=004whilei<f.len:05iff[i]=='$':06casef[i+1]07of'1'..'9':08varj=009i+=110whilef[i]in{'0'..'9'}:11j=j*10+ord(f[i])-ord('0');i+=112result.add(a[j-1])13else:14invalidFormatString()15else:16result.add(newLit(f[i]));i+=1

Implements this optimization:

"$1: $2\n"%["s",$obj.s]-->`&`("s",':',' ',$obj.s,'\n')
result="("result.add("$1: $2\n"%["a",$obj.a])result.add("$1: $2\n"%["b",$obj.b])result.add("$1: $2\n"%["s",$obj.s])result.add(")")

After partial evaluation:

result="("result.add(`&`("a",':',' ',$obj.a,'\n'))result.add(`&`("b",':',' ',$obj.b,'\n'))result.add(`&`("s",':',' ',$obj.s,'\n'))result.add(")")
result="("result.add(`&`("a",':',' ',$obj.a,'\n'))result.add(`&`("b",':',' ',$obj.b,'\n'))result.add(`&`("s",':',' ',$obj.s,'\n'))result.add(")")

After constant folding:

result="("result.add(`&`("a: ",$obj.a,'\n'))result.add(`&`("b: ",$obj.b,'\n'))result.add(`&`("s: ",$obj.s,'\n'))result.add(")")

After constant folding:

result="("result.add(`&`("a: ",$obj.a,'\n'))result.add(`&`("b: ",$obj.b,'\n'))result.add(`&`("s: ",$obj.s,'\n'))result.add(")")

Further optimization via term rewriting templates:

templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&ztemplateoptAdd2{x.add(y);x.add(z)}(x,y,z:string)=x.add(y&z)
result="("result.add(`&`("a: ",$obj.a,'\n'))result.add(`&`("b: ",$obj.b,'\n'))result.add(`&`("s: ",$obj.s,'\n'))result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z
result="("&`&`("a: ",$obj.a,'\n')result.add(`&`("b: ",$obj.b,'\n'))result.add(`&`("s: ",$obj.s,'\n'))result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z
result="("&`&`("a: ",$obj.a,'\n')result.add(`&`("b: ",$obj.b,'\n'))result.add(`&`("s: ",$obj.s,'\n'))result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z
result="("&`&`("a: ",$obj.a,'\n')&`&`("b: ",$obj.b,'\n')result.add(`&`("s: ",$obj.s,'\n'))result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z
result="("&`&`("a: ",$obj.a,'\n')&`&`("b: ",$obj.b,'\n')result.add(`&`("s: ",$obj.s,'\n'))result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z
result="("&`&`("a: ",$obj.a,'\n')&`&`("b: ",$obj.b,'\n')&`&`("s: ",$obj.s,'\n')result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z
result="("&`&`("a: ",$obj.a,'\n')&`&`("b: ",$obj.b,'\n')&`&`("s: ",$obj.s,'\n')result.add(")")templateoptAdd1{x=y;x.add(z)}(x,y,z:string)=x=y&z

After applying these rules the code is:

result="("&`&`("a: ",$obj.a,'\n'))&`&`("b: ",$obj.b,'\n'))&`&`("s: ",$obj.s,'\n'))&")"

After constant folding (that the compiler performs for us) it becomes:

result=`&`("(a: ",$obj.a,"\nb: ",$obj.b,"\ns: ",$obj.s,"\n)")
  • Implementation of % and optFormat look suspiciously alike.
  • Can we avoid the code duplication?
01templateformatImpl(handleChar:expr)=02vari=003whilei<f.len:04iff[i]=='$':05casef[i+1]06of'1'..'9':07varj=008i+=109whilef[i]in{'0'..'9'}:10j=j*10+ord(f[i])-ord('0');i+=111result.add(a[j-1])12else:13invalidFormatString()14else:15result.add(handleChar(f[i]));i+=11617proc`%`(f:string,a:openArray[string]):string=18templateidentity(x:expr):expr=x19result="";formatImpl(identity)2021macrooptFormat{`%`(f,a)}(f:string{lit},a:openArray[string]):expr=22result=newCall("&");formatImpl(newLit)
01procre(x:string):Regex=020304templateoptRe{re(x)}(x:string{lit}):Regex=05varg{.global.}=re(x)06g0708template`=~`(s:string,pattern:Regex):bool=09whennotdefinedInScope(matches):10varmatches{.inject.}:array[maxSubPatterns,string]11match(s,pattern,matches)1213forlineinlines("input.txt"):14ifline=~re"(\w+)=(\w+)":15echo"key-value pair; key: ",matches[0]," value: ",matches[1]
  • Perl performs same optimization; however regexes are built into Perl, but not into Nimrod
  • 'inject' breaks hygiene

You name it, Nimrod got it (except fexprs ;-):

  • compile time function evaluation; including staticRead and staticExec
  • declarative (template) and imperative (macro) AST based macros: both hygienic and dirty
  • term rewriting macros; side-effect and alias analysis constraints
  • source code filters
  • programmable annotation system ("pragmas")
01macrocheck(ex:expr):stmt=02varinfo=ex.lineInfo03varexpString=ex.toStrLit04result=quotedo:05ifnot`ex`:06echo`info`,": Check failed: ",`expString`0708check1<2

Thank you for listening. We are always looking for contributors:


Viewing all articles
Browse latest Browse all 737

Trending Articles