quasi-literal strawman

Mike Samuel mikesamuel at gmail.com
Wed Dec 23 09:40:57 PST 2009


CC [+es-discuss]

2009/12/23 Kevin Curtis <kevinc1846 at googlemail.com>:
> On Wed, Dec 23, 2009 at 2:57 PM, Mike Samuel <mikesamuel at gmail.com> wrote:
>>> Re quasi-literal and error handling - if the function called by the
>>> quasi throws an error how is it reported? Can the specific character
>>> position in the quasi that caused the error be reported?
>>> re`\s+$$$baz\s+`
>>>        ^
>>
>> This is a good point.  To determine the location of a given character,
>> a quasi function needs to know the width of the preceding
>> substitutions and positions of any $\ escapes, which it does not
>> currently know.
>>
>
> Also, to report the error position in terms of the script overall
> would it be necessary for the character pos/line number of the Error
> object to be set. This would be possible if quasi's at compile time
> had:
> - access to the ast tree to set the pos value directly
> - access to pragmas to pass the pos through to the ast. See:
> https://mail.mozilla.org/pipermail/es-discuss/2009-November/010105.html

Funny.  I was actually just writing up an alternative implementation
based on JSON ASTs to send to some macro-loving schemers.
That might address the error reporting problem, and allows quasis to
specify new flow control constructs by allowing deferred execution,
without breaking the static analyzability or backwards portability.

Possible alternative:

Transform
    foo`baz${bar}`

using JSON ASTs to a hygienic macro as in:

eval(
    verifyHygienic(
        foo('baz', ''),
        [
            Object.freeze(['Reference', { identifier: 'bar' }])
        ],
        []
    )
)

where the lists are capabilities.  The use of an expression in a
substitution grants authority to use that in an AST produced by the
quasi function.  The AST produced by the quasi function is not allowed
to have any free variables besides those in substitutions, and (still
TODO) a reference to the quasi function itself which provides access
to supporting code defined with the quasi function.

The verifier looks like,

function freeNamesExcluding(
    ast,          // The AST whose free names are put on out.
    exclusions,   // Subtrees to exclude, compared by identity (===).
    outNames,     // Receives free names.
    outLabels) {  // Receives free labels.
  if (exclusions.indexOf(ast) >= 0) { return; }  // Compared by identity.
  ...
}

// Returns an AST that is reference identical unless a name needs to
// be rewritten.
function alphaRenameExcluding(
    ast,
    scope,
    readContext,  // True if ast is read, e.g. not the left operand of =
    writeContext,  // True if ast is a LeftHandSideExpression.
    identGenerator,
    readExclusions,
    writeExclusions) {
  if ((readContext && readExclusions.indexOf(ast) >= 0)
      || (writeContext && writeExclusions.indexOf(ast) >= 0)) {
    return ast;  // Preserve capabilities
  }
  // Sample each property from ast exactly once.  See "Lying objects"
  // below.

  // If introduces a scope, create a new scope and recurse to find all
  // declarations, and use identGenerator to assign new identifiers for
  // names and labels.

  // Recurse and build a list of renamed children.
  // Only writeContext should be true for the first child of '=' ops, and
  // for(in) loops, and declaration names.
  // Set both readContext and writeContext for the first child of '+=' et al.
  // The second child of '.' op should have neither read nor write context set.
  // For all others, set readContext but not writeContext.

  // Return a new AST node using renamed children.
  // If (readContext || writeContext), rename using name and label
  // values from scope.
}

function verifyHygienic(astMaker, astReadCaps, astWriteCaps) {
  Object.freeze(astReadCaps);
  Object.freeze(astWriteCaps);
  // Compute set of free names in astCaps
  var freeNamesInCaps = {};
  var freeLabelsInCaps = {};
  var allAstCaps = union(astReadCaps, astWriteCaps);
  each(
      allAstCaps,
      function (ast) {
        freeNames(ast, freeNamesInCaps, freeLabelsInCaps);
      });
  var allCapIdents = union(freeNamesInCaps, freeLabelsInCaps);
  // Produces distinct identifiers that do not conflict with language
  // keywords (reserved or otherwise) or with any identifier free
  // in the input ASTs.
  var namer = function nameGenerator() {
    var counter = 0;
    return function () {
      var name;
      do {
        name = '$' + counter;  // Disjoint from keyword namespace.
      } while (name in allCapIdents);
      return name;
    };
  }();
  // Delegate to the untrusted astMaker function to produce an AST.
  var outAst = astMaker(astReadCaps, astWriteCaps);
  // Make sure outAst is hygienic.
  // This has the side effect of copying the AST nodes created by astMaker,
  // which insulates against lying objects like:
  //     return [
  //       'binary',
  //       { get operator() { return (++counter & 1) ? '-' : '-='; } },
  //       ... ]
  outAst = alphaRenameExluding(
      outAst, {}, true, false, namer, astReadCaps, astWriteCaps);
  // Compute the set of free names excluding those in the input capabilities.
  // In the alpha-renaming stage, we declined to rename things in caps only
  // when they appeared in the appropriate read/write context, so names in
  // allCapIdents cannot appear outside the correct read/write context.
  // All other free identifiers must have been produced by namer, and if those
  // are free, then they could conflict with the containing scope violating
  // hygiene.
  // TODO: this does not verify that any declarations in caps are declared
  // in a scope that contains all uses of the same name in caps.
  var freeNames = {};
  var freeLabels = {};
  freeNames(outAst, allAstCaps, freeNames, freeLabels);
  var freeIdents = setSubtract(union(freeNames, freeLabels), allCapIdents);
  if (empty(freeIdents)) { return outAst; }
  // TODO: preserve a mapping from alpha renamed identifiers to original
  // for error message.
  throw new Error('Macro includes free variables ' + keys(freeIdents));
}





> Heres how the native /regex/ syntax reports errors on V8:
>
> kevin at k-laptop:~/code/v8test$ more zreg.js
> print("--- start");
> var z = /((((/g;
> print("--- end");
> kevin at k-laptop:~/code/v8test$ ./shell_g zreg.js
> --- start
> native regexp.js:101: SyntaxError: Invalid regular expression: /((((/:
> Unterminated group
> %RegExpCompile(a,b,d);
> ^
>
> Should a regex quasi offer the same functionality?
> The reporting of the position of the errror between engines seems all
> over the place. Not sure if there  is any plans to address this.
>
> Regards.
>


More information about the es-discuss mailing list