Quasis: assignable substitutions?

Mike Samuel mikesamuel at gmail.com
Mon Jun 20 09:17:15 PDT 2011


2011/6/18 Mark S. Miller <erights at google.com>:
> On Sat, Jun 18, 2011 at 5:59 PM, Mike Samuel <mikesamuel at gmail.com> wrote:
> [...]
>>
>> >> What idiom should quasi handler authors use to distinguish a writable
>> >> slot from a read-only slot?
>
> [...]
>>
>> By read-only slot vs read-write slot I meant
>> example`
>>  ${x} is read only
>>  ${=y} is read-write
>> `
>>
>> I should have phrased that as read-only substitution vs read-write
>> substitution using the alternate slot spec for SVE.
>
> I think this is really just a special case of the general question "What
> idiom should quasi handler authors use to distinguish what kind of value is
> expected in a given hole position?" There's no general answer, and the
> answer for a particular quasi handler depends on the user's understanding of
> the purpose of the quasi handler. If the kind of thing expected at a given
> hole position is a record with a zero argument get method and/or a one
> argument set method, then ${=x} is just a particularly convenient method for
> generating one on the fly whose two methods happen to provide read-write
> access to x.
> That said, for each hole position, the user's understanding of the meaning
> of the quasi handler will usually be one of "consumes a value here", in
> which case ${x} would be natural, or "extracts a value from the specimen and
> assigns it here", in which case ${=x} would be natural.
> Returning to a recent example, for regex capture positions, i.e., after an
> unescaped "(", I'd always expect to see ${=x} unless something unusual is
> going on. Elsewhere, I'd expect ${x} to provide text to match against. If we
> use this convention, then to provide text to match against after an
> unescaped "(" that appears for other reasons, you'd use "(?:" instead. I
> admit this is accident prone, but is I think still the best balance.

I can imagine that in

   re`foo(${bar}.)baz`.match(str)

where bar="BAR" the user would expect this to do

    new RegExp("foo(BAR.)baz").exec(str)

making no assignments.

While with

   re`foo(${=bar}.baz`

the user would expect this to do something like

    (function () {
      var match = new RegExp("foo(.)baz").exec(str);
      if (match) {
        bar = match[1];
      }
      return match;
    }())


The quasi handler could be implemented as below.  See the parts where
I test "function" === typeof subst.

    function (callSiteId /*, sve... */) {
      var pattern, exec, flags;
      // Cache fetch of pattern, exec and flags elided.

      // Build the pattern and flags.
      var raw = callSiteId.raw;
      var substs = callSiteId.subst;  // Slots/thunks stored on
callSiteId somehow.
      var pattern = "";
      var lastIndex = raw.length - 1;
      for (var i = 0; i < lastIndex;) {
        var subst = substs[i];
        pattern += raw[++i]
          // Idiom to test whether a thunk or a slot.
          ("function" === typeof subst ? regexpEncode(subst()) : "");
      }
      // The flags might be glommed onto the last item, e.g.
      //   re`foo#i`
      // or some similar syntax.
      var last = raw[lastIndex];
      flags = // extract flags from last
      pattern += last;

      // For each group index besides 0, an optional function that
receives its value.
      var groupReceivers;
      // Define an exec method that assigns groups to recievers on match.
      exec = function (str) {
        var match = RegExp.prototype.exec.apply(this, arguments);
        if (match && match.length > 1) {
          if (!groupReceivers) {
            groupReceivers = [];
            for (var i = 0; i < lastIndex; ++i) {
              var subst = substs[i];
              if ("function" !== typeof subst && "function" === typeof
subst.set) {
                groupReceivers[i + 1] = subst.set;
              }
            }
          }
          for (var i = 1; i < match.length; ++i) {
            var groupReceiver = groupReceivers[i];
            if (groupReceiver) { groupReceiver[i].call(null, match[i]); }
          }
        }
        return match;
      };
      // Freeze of exec and its prototype elided.

      // Cache put of pattern, flags, exec elided.

      // Create a regex with an overridden exec.
      var re = new RegExp(pattern, flags);
      re.exec = exec;
      return re;
    }


More information about the es-discuss mailing list