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代码。

相关文章
相关标签/搜索