Search code examples
javascriptnode.jssweet.js

How to break SweetJS hygiene for local variable?


I am attempting to use SweetJS in my project. In order to better understand and learn SweetJS I thought I would start with a simple "class" macro (I know a few exist, just playing around here...). I can NOT seem to get SweetJS to stop messing with my local variables "self" and "superCall" however. Any ideas what I am doing wrong? I would like var self=this to remain var self=this instead of being mangled.

macro class {
  case { _ $name extends $parent {
    constructor $cargs { $cbody ... }
    $($mname $margs { $mbody ... } ) ... 
  } } => {
    return #{
        function $name $cargs { var self=this,superCall=$parent.prototype; $cbody ... }
        $name.prototype = Object.create($parent.prototype);
        ($name.prototype.$mname = function $margs {var self=this,superCall=$parent.prototype; $mbody ... } ) ...;
    }
  }

  case { _ $name { $body ...} } => {
    return #{ class $name extends test2 { $body ... } };
  }
}

macro super {
    case { $macroName.$name( $($args (,) ...) ) } => {
       letstx $s = [makeIdent("self", #{ $macroName })];
       letstx $sC = [makeIdent("superCall", #{ $macroName })];
       return #{
          $sC.$name.call($s)
       };
    }

    case { $macroName( $args ... ) } => {
       letstx $s = [makeIdent("self", #{ $macroName })];
       letstx $sC = [makeIdent("superCall", #{ $macroName })];
       return #{
          superCall.constructor.call($s);
       };
    }
}

class test extends cow {
 constructor(arg1, arg2) {
     console.log('Hello world!');
 }
 method1(arg1, arg2) {
     super.method1();
 }
}

This expands to:

function test(arg1, arg2) {
    var self$2 = this, superCall$2 = cow.prototype;
    console.log('Hello world!');
}
test.prototype = Object.create(cow.prototype);
test.prototype.method1 = function (arg1, arg2) {
    var self$2 = this, superCall$2 = cow.prototype;
    superCall.method1.call(self);
};

As you can see, var self=this has been turned into var self$2 = this. How can I prevent this? I have attempted to use makeIdent, but I think I am doing something wrong. Any ideas? Thanks!


Solution

  • In order to break hygiene you need to provide the lexical context that is outside the scope of the macro you are in. In this case, by using the $name binding, you are actually referencing scope outside of your macro rather than from within; this makes breaking hygiene possible in this case.

    As a result, the following seems to work:

    macro class {
      case { _ $name extends $parent {
        constructor $cargs { $cbody ... }
        $($mname $margs { $mbody ... } ) ... 
      } } => {
        letstx $self = [makeIdent("self", #{ $name })];
        return #{
            function $name $cargs { var $self=this,superCall=$parent.prototype; $cbody ... }
            $name.prototype = Object.create($parent.prototype);
            ($name.prototype.$mname = function $margs {var $self=this,superCall=$parent.prototype; $mbody ... } ) ...;
        }
      }
    
      case { _ $name { $body ...} } => {
        return #{ class $name extends test2 { $body ... } };
      }
    }
    

    Notice that I created an identifier named $self and used the name of the class as my syntax object.

    Read more about breaking hygiene here.