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;
}
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
https://github.com/mozilla/sweet.js
npm install -g sweet.js
sweet.js intern presentation
By disnet
sweet.js intern presentation
- 1,857