sweet.js中文資料

sweet.js 帶給javascript 相似Scheme and Rust語言中的衛生宏功能
宏容許你定製甜美的js語法,製成您求之不得的專用js:javascript

macro def {
  case $name:ident $params $body => {
    function $name $params $body
  }
}

def add (a, b) {
  return a + b;
}

接下來是類的實現:java

macro class {
  case $className:ident { 
    constructor $constParam $constBody
    $($methodName:ident $methodParam $methodBody) ... } => {

    function $className $constParam $constBody

    $($className.prototype.$methodName 
      = function $methodName $methodParam $methodBody; ) ...
  }
}

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

  say(msg) {
    console.log(this.name + " says: " + msg);
  }
}
var bob = new Person("Bob");
bob.say("Macros are sweet!");

能夠把macros看做是語法層面的函數. 像普通函數同樣,你定義一個宏,而後用語法參數調用他,獲得一個新的語法. 運行sweet.js宏,將會展開全部的宏定義,生成可運行在任何js環境中的原生js。git

兩種方式定義宏: 簡單的基於模式匹配的規則宏 and 更牛的程式判斷的宏(熟悉Scheme or Racket的話,他們分別對應着 syntax-rules 和 syntax-case).github

基於模式的宏,規則:

Rule macros 原理:匹配一個語法模式,獲得一個基於模板的新語法.數組

macro <name> {
  rule { <pattern> } => { <template> }
}
macro id {
  rule {
    // after the macro name, match:
    // (1) a open paren 
    // (2) a single token and bind it to `$x`
    // (3) a close paren
    ($x)
  } => {
    // just return the token that is bound to `$x`
    $x
  }
}
id (42)
// --> expands to
42

在模版中,$開頭的模式匹配任意的 token,綁定到name上.ruby

注意到:一個token包括逗號,而不單單是數字或者標識符,例如數組元素被認爲是一個完成的tokenless

id ([1, 2, 3])
// --> expands to
[1, 2, 3]

衛生宏如何保持乾淨

好比交換兩個數的值ide

macro swap {
  rule { {$a <=> $b} } => {
    var tmp = $a;
    $a = $b;
    $b = tmp;
  }
}

var a = 10;
var b = 20;

swap {a <=> b}

以上被轉化爲函數

var a$1 = 10;
var b$2 = 20;

var tmp$3 = a$1;
a$1 = b$2;
b$2 = tmp$3;

加上相似$n的後綴,是爲了不下面的狀況:ui

var tmp = 10;
var b = 20;

swap {tmp <=> b}

// --> naive expansion
var tmp = 10;
var b = 20;

var tmp = tmp;
tmp = b;
b = tmp;

有了衛生處理就不會弄錯了:

var tmp = 10;
var b = 20;

swap {tmp <=> b}

// --> hygienic expansion
var tmp$1 = 10;
var b$2 = 20;

var tmp$3 = tmp$1;
tmp$3 = b$2;
b$2 = tmp$1;

想打破這個規則,能夠看下一節;

模式和宏——天造地設的一對

解析類的使用:

macro m {
  rule { ($x:expr) } => {
    $x
  }
}
m (2 + 5 * 10)
// --> expands to
2 + 5 * 10

解析類目前支持:

  • expr—— 匹配一個表達式
  • ident—— 一個標識符匹配
  • ilt—— 匹配文字

可重複的模式:

macro m {
  rule { ($x ...) } => {
    // ...
  }
}
m (1 2 3 4)

要支持,? 加上(,)

macro m {
  rule { ($x (,) ...) } => {
    [$x (,) ...]
  }
}
m (1, 2, 3, 4)

用$()來支持模式組

macro m {
  rule { ( $($id:ident = $val:expr) (,) ...) } => {
    $(var $id = $val;) ...
  }
}
m (x = 10, y = 2+10)

多條規則也沒問題

macro m {
  rule { ($x:lit) } => { $x }
  rule { ($x:lit, $y:lit) } => { [$x, $y] }
}

m (1);
m (1, 2);

規則按順序直到匹配成功,因而宏能夠遞歸定義啦:

macro m {
  rule { ($base) } => { [$base] }
  rule { ($head $tail ...) } => { [$head, m ($tail ...)] }
}
m (1 2 3 4 5)  // --> [1, [2, [3, [4, [5]]]]]

宏程式開始

更厲害的case macro能夠用徹底的js能力來處理語法:

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

case macro 和rule macro不一樣之處,macro name也會被匹配到,而不光是其後的body內容:

macro m {
  case { $name $x } => { ... }
}
m 42  // `$name` will be bound to the `m` token
      // in the macro body

用_能夠匹配任意的token,並忽略綁定。

macro m {
  case { _ $x } => { ... }
}

另外一個不一樣之處是case macro的body部分包含一個模板和js的混合體,用來建立和操縱語法,下面是一個身份定義宏:

macro id {
  case {_ $x } => {
    return #{ $x }
  }
}

#{...}是syntax{...}的縮寫,他建立一個【在模式匹配綁定做用域內的】語法對象數組

語法對象是sweet.js用於詞法上下文【保持變量衛生】的聲明式的token,能夠用#{}模板建立,也能夠用現有對象的詞法上下文來建立獨特的詞法對象

macro m {
  case {_ $x } => {
    var y = makeValue(42, #{$x});
    return [y]
  }
}
m foo
// --> expands to
42

有如下內置的建立語法對象的函數

  1. makeValue(val, stx) – val 能夠是 boolean, number, string, or null/undefined
  2. makeRegex(pattern, flags, stx) – pattern 是regex pattern的字符串表示 flags是regex flags的字符串表示
  3. makeIdent(val, stx) – val 是一個表明一個標識符identifier的字符串
  4. makePunc(val, stx) – val 是一個表明一個標點符號punctuation 的字符串 (e.g. =, ,, >, etc.)
  5. makeDelim(val, inner, stx) – val是分隔符 能夠是"()", "[]", or "{}" 或者內部是一個語法對象syntax objects數組的全部tokens的內部分隔符.

(這些函數大體等於 Scheme/Racket 中的函數 datum->syntax)

若是要暴露語法對象在詞法上下文中,以直接獲得token,能夠用unwrapSyntax(stx)(對應Scheme的syntax-e)。

用這些函數建立的新語法對象,很方便就能夠在#{}模版中引用. 使用提供的letstx宏,綁定語法對象syntax objects到模式匹配變量:

macro m {
  case {_ $x } => {
    var y = makeValue(42, #{$x});
    letstx $y = [y], $z = [makeValue(2, #{$x})];
    return #{$x + $y - $z}
  }
}
m 1
// --> expands to
1 + 42 - 2

愈來愈髒,破壞衛生

打破衛生的方法是經過在「正確的位置」從語法對象上偷取詞法上下文,To clarify, consider aif the anaphoric if macro that binds its condition to the identifier it in the body.

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

This is a violation of hygiene because normally it should be bound to the surrounding environment ("foo" in the example above) but aif wants to capture it. To do this we can create an it binding in the macro that has the lexical context associated with the surrounding environment. The lexical context we want is actually found on the aif macro name itself. So we just need to create a new it binding using the lexical context of aif:

macro aif {
  case {
    // bind the macro name to `$aif_name`
    $aif_name 
    ($cond ...) {$body ...}
  } => {
    // make a new `it` identifier using the lexical context
    // from `$aif_name`
  var it = makeIdent("it", #{$aif_name});
  letstx $it = [it];
  return #{ 
      // create an IIFE that binds `$cond` to `$it`
      (function ($it) {
        if ($cond ...) {
          // all `it` identifiers in `$body` will now
          // be bound to `$it` 
          $body ...
        }
      })($cond ...);
    }
  }
}

讓它成爲…不遞歸

有時候不想要宏遞歸自身,好比要在函數執行前添加一些日誌:

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

他將永遠循環下去,由於宏展開中的函數標識符綁定到了函數宏自身,阻止這個狀況要使用let宏來綁定form表

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

他將function綁定到了宏本體body,而不是宏本體body內部

帶反查的宏,當心你的背後

你能夠用中綴infix規則匹配以前的語法,用(|) 分離左右兩邊。

macro unless {
  rule infix { return $value:expr | $guard:expr } => {
    if (!($guard)) {
      return $value;
    }
  }
}

function foo(x) {
  return true unless x > 42;
  return false;
}

infix能夠用於程式宏,宏名字是右側第一個token

macro m {
  case infix { $lhs | $name $rhs } => { ... }
}

infix規則能夠和正常的規則混合

macro m {
  rule infix { $lhs | $rhs } => { ... }
  rule { $rhs } => { ... }
}

infix盡力處理關閉以前的語法

macro m {
  rule infix { ($args ...) | $call:expr } => {
    $call($args ...)
  }
}

(42) m foo; // This works
bar(42) m foo; // This is a match error

Sweet.js目前支持聲明式的宏定義,然而據Mozilla研究所的Tim Disney所說,計劃將要支持命令式的定義。這意味着宏能夠包含編譯時運行的任意JavaScript代碼。

相關文章
相關標籤/搜索