Sweet.js Macros

Harder, Better, Faster, Stronger





Tim Disney

Overview


what is it?


  • Hygienic macro system for JavaScript
  • Compiles JS+Macros to pure JS
  • Design inspired by Scheme/Racket and Rust
    • Not cpp
  • Started as an internship project last summer

How does it work?

Match a pattern then generate a template

macro <name> {  rule { <pattern> } => { <template> }} 

Identity macro:
macro id {
  rule { ($x) } => { $x }
}
id (42)
// compiles to
42

REPETITION 

macro def {
  rule { 
    $name ($params ...) { $body ... } 
  } => {
    function $name ($params ...) { $body ...}
  }
}
def add (a, b) {
  return a + b;
}
Compiles to:
function add(a, b) {  return a + b;}

class

Classes are in ES6 but I want them now!

class Person {
  constructor(name) {
    this.name = name;
  }

  say(msg) {
    console.log(this.name + " says: " + msg);
  }
}
Simple desugaring:
function Person(name) { this.name = name; }Person.prototype.say = function(msg) {  console.log(this.name + " says: " + msg);} 

class

$() will group multiple tokens to repeat

macro class {
  rule {
    $className {
        constructor ($cparams ...) {$cbody ...}
        $($mname ($mparams ...) {$mbody ...} ) ...
    }
  } => {
    function $className ($cparams ...) {$cbody ...}
    $($className.prototype.$mname
      = function $mname ($mparams ...) {$mbody ...}; ) ...

  }
}

what's new?



  • Last summer:
    • Figured out read with Paul Stansifer
    • Implemented rule macros
  • This summer:
    • Lots of bugs squashed
      • in particular in read (see issue #82)
    • Implemented case macros

case macros


Slightly different form from rule macros:

macro <name> {
  case { <pattern> } => { <body> }
}

<body> is now JavaScript code that is 
evaluated when the macro gets invoked

case macros


Templates are created with #{}

So the identity macro is now:
macro id {
  case {_ $x } => {
    return #{ $x }
  }
}

case macros

Functions are provided to create new syntax

macro m {
  case {_ () } => {
    return [makeValue(42, #{here})]
  }
}
m ()
// --> expands to
42
  • makeValue - booleans, numbers, strings
  • makeIdent - identifiers
  • makeRegex - regexps
  • makePunc - "Punctuators" (ie +, -, /, etc.)
  • makeDelim - {}, [], ()

 

case macros

Use "withSyntax" to mix templates and syntax creation

macro m {
  case {_ $x } => {
    return withSyntax($y = [makeValue(2, #{here})]) {
      return #{$x + $y}
    }
  }
}
m 1
// --> expands to
1 + 2

Let Bound macros


Prevents the macro from being bound in the body

let function = macro {  case {_ $name $params { $body ... } } => {    return #{
function $name $params {
console.log("Function called..."); $body ... } }
}
}

Infinite loop if we use "macro function { ... }" 

Putting it all together


Let's make if anaphoric:

long.obj.path = [1, 2, 3];if (long.obj.path) {
  console.log(it);
}
// logs: [1, 2, 3]

This is unhygienic!
(but kinda cool...)

anaphoric if


let if = macro {
  case {
    $if_name 
    ($cond ...) {$body ...}
  } => {
    return withSyntax($it = [makeIdent("it", #{$if_name})]) {
      return #{ 
        var $it = $cond ...
        if ($it) {
          $body ...
        }
      }
    }
  }
}


Why macros?


  • Success of compile-to-JS languages
    • CoffeeScript thin sugar on top of JS
    • Set of programmers that want new syntax
  • Community language design
    • Don't wait for TC39
    • Fill narrow niche

Future work


  • Sourcemaps
  • Improve error messages
  • Prove read is correct
  • Integration with modules

Get it



http://sweetjs.org/

https://github.com/mozilla/sweet.js


npm install -g sweet.js
Made with Slides.com