Skip to content

Use sanitized identifiers in generated code #92

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
egonelbre opened this issue Jun 15, 2012 · 9 comments
Closed

Use sanitized identifiers in generated code #92

egonelbre opened this issue Jun 15, 2012 · 9 comments
Assignees
Labels
Milestone

Comments

@egonelbre
Copy link

When writing rules or identifiers it's really easy to create a name clash between reserved javascript words or internal variables used by the generators.

For example when using the online parser generator this rule :

 start = int:[0-9]+ offset:[a-z] { return int + offset }

Easiest solution would be just to use prefixed (ie. "_" or "$") identifiers in the generated code.

@michaelficarra
Copy link

I vote for $. It was originally allowed in identifiers specifically for this purpose.

@dmajda
Copy link
Contributor

dmajda commented Jun 16, 2012

It's a pity that JavaScript does not have better scope controlling mechanism (ability for an inner function not to inherit its lexical environment).

@michaelficarra I don't think $ is the best choice. For example, Angular framework has lots of $-variables. Some people also like to save DOM elements into variables starting with $. Clashes would be still likely if PEG.js used $ as a prefix.

ATM I am leaning towards the __ prefix which conforms to an old C/C++ tradition (one _ means programmer's internal stuff, two _'s mean implementation/library internal stuff).

@egonelbre
Copy link
Author

Javascript also uses __ internally, for example __defineGetter__. Also it's always possible to use longer prefix: peg$ or var$ or lbl$. Or just keep it configurable and use some sensible default.

@dmajda
Copy link
Contributor

dmajda commented Jun 19, 2012

Also it's always possible to use longer prefix: peg$ or var$ or lbl$.

The peg$ prefix sounds good.

Or just keep it configurable and use some sensible default.

No way this is going to be configurable :-)

@dignifiedquire
Copy link

Why not create an object, e.g. named peg so that you could do something like

start = int:[0-9]+ offset:[a-z] { return peg.int + peg.offset }

@dmajda
Copy link
Contributor

dmajda commented Sep 22, 2012

Why not create an object, e.g. named peg...

Performance. Every property access costs a lot of time compared to plain variable access (I did some benchmarks some time ago).

@ghost ghost assigned dmajda Nov 22, 2012
dmajda added a commit that referenced this issue Jan 1, 2013
This is a complete rewrite of the PEG.js code generator. Its goals are:

  1. Allow optimizing the generated parser code for code size as well as
     for parsing speed.

  2. Prepare ground for future optimizations and big features (like
     incremental parsing).

  2. Replace the old template-based code-generation system with
     something more lightweight and flexible.

  4. General code cleanup (structure, style, variable names, ...).

New Architecture
----------------

The new code generator consists of two steps:

  * Bytecode generator -- produces bytecode for an abstract virtual
    machine

  * JavaScript generator -- produces JavaScript code based on the
    bytecode

The abstract virtual machine is stack-based. Originally I wanted to make
it register-based, but it turned out that all the code related to it
would be more complex and the bytecode itself would be longer (because
of explicit register specifications in instructions). The only downsides
of the stack-based approach seem to be few small inefficiencies (see
e.g. the |NIP| instruction), which seem to be insignificant.

The new generator allows optimizing for parsing speed or code size (you
can choose using the |optimize| option of the |PEG.buildParser| method
or the --optimize/-o option on the command-line).

When optimizing for size, the JavaScript generator emits the bytecode
together with its constant table and a generic bytecode interpreter.
Because the interpreter is small and the bytecode and constant table
grow only slowly with size of the grammar, the resulting parser is also
small.

When optimizing for speed, the JavaScript generator just compiles the
bytecode into JavaScript. The generated code is relatively efficient, so
the resulting parser is fast.

Internal Identifiers
--------------------

As a small bonus, all internal identifiers visible to user code in the
initializer, actions and predicates are prefixed by |peg$|. This lowers
the chance that identifiers in user code will conflict with the ones
from PEG.js. It also makes using any internals in user code ugly, which
is a good thing. This solves GH-92.

Performance
-----------

The new code generator improved parsing speed and parser code size
significantly. The generated parsers are now:

  * 39% faster when optimizing for speed

  * 69% smaller when optimizing for size (without minification)

  * 31% smaller when optimizing for size (with minification)

(Parsing speed was measured using the |benchmark/run| script. Code size
was measured by generating parsers for examples in the |examples|
directory and adding up the file sizes. Minification was done by |uglify
--ascii| in version 1.3.4.)

Final Note
----------

This is just a beginning! The new code generator lays a foundation upon
which many optimizations and improvements can (and will) be made.

Stay tuned :-)
@dmajda
Copy link
Contributor

dmajda commented Jan 1, 2013

This should be fixed by the code generator rewrite. Thanks @egonelbre for the peg$ prefix tip!

@dmajda dmajda closed this as completed Jan 1, 2013
@shamansir
Copy link

May be one more hint will help you resolve it nicer (a one from my functional-version of your generator):

(function() {

  // here are some global variables accessible both to user 
  // code and to parser, like pos and line and stuff (not required
  // to be initialized, just names)
  var pos, line, ...;

  // the very one variable a name of which may clash with user code 
  var user$ = (function() {
    // it is a closure to hold all user variables in one context 
    // without access to parser inner variables

    return function() {

      // user initializer code, if specified, with only one restriction: 
      // this one is required not to have any first-level returns inside
      ...

      // returns an object or something that stores all of the user code 
      // blocks in parser, in any manner you want
      return { 'rule1': [ function(<code_params>) { ... }, ... ]
               ... }; 

    };

  })();

  return (function() {

    // all private parser-related code and vars go here

    var code_blocks = user$(); // this one calls user initializer
                               // and returns his code blocks

    // do anything you want with code_blocks
    code_blocks['rule1'][0](...);

    // parser code
    ...

    return { /* parser object */ };

  })();

})();

The only bottle-neck here I see is an object to access code blocks (I store them like user$['start'][2]), but it is used not so often to be nervous on a speed, and requires just one more round when generating a parser to collect parameters names and match code blocks id's to a places where they are used. Also, you may get an array of code blocks once just when starting to execute a rule.

@dmajda
Copy link
Contributor

dmajda commented Jan 4, 2013

@shamansir Yeah, there are other possible solutions. I chose the simplest one because I wanted to get the new generator done before the end of my Christmas break and I didn't have much time to experiment toward the end.

As for the array access, at some point I measured the difference between c0, c1, etc. vs. c[0], c[1], etc. There was a significant slowdown (I don't remeber the exact numbers though). That was for all consts though, the results just for action code would be different.

Anyway, I'll look at the genreator again in the 0.9.0 timeframe, now I have different priorities. Feel free to file issues for anything you feel could be improved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants