76 Commits (c0df01b092a7359dbcf6acfee0d6bc1497087830)

Author SHA1 Message Date
David Majda 95fd64ec15 .jshintrc: Add the "forin" option & fix fallout
Also added few missing |hasOwnProperty| calls that JSHint didn't detect
because it only looks whether there is an |if| statement wrapping the
loop body.
11 years ago
David Majda e4b5588327 Plugin API: Split compiler passes into stages
The compiler passes are now split into three stages:

  * check -- passes that check for various error conditions

  * transform -- passes that transform the AST (e.g. to perform
    optimizations)

  * generate -- passes that are related to code generation

Splitting the passes into stages is important for plugins. For example,
if a plugin wants to add a new optimization pass, it can add it at the
end of the "transform" stage without any knowledge about other passes it
contains. Similarly, if it wants to generate something else than the
default code generator does from the AST, it can just replace all passes
in the "generate" stage by its own one(s).

More generally, the stages make it possible to write plugins that do not
depend on names and actions of specific passes (which I consider
internal and subject of change), just on the definition of stages (which
I consider a public API with to which semver rules apply).

Implements part of GH-106.
11 years ago
David Majda d02098eebe Plugin API: Implement the |passes| parameter for |PEG.compiler.compile|
The |passes| parameter will allow to pass the list of passes from
|PEG.buildParser|. This will be used by plugins. The old way via setting
the |appliedPassNames| property is removed.

Implements part of GH-106.
11 years ago
David Majda 3b3798fa39 Merge lib/compiler/passes.js into lib/compiler.js
It didn't make sense to have the passes in a separate file.
11 years ago
David Majda d3d4ace153 Move options handling from passes to |PEG.compiler.compile|
This eliminates some duplicate code.
11 years ago
David Majda fe1ca481ab Code generator rewrite
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 :-)
11 years ago
David Majda 05a6bad989 Kill the |toSource| method, introduce the |output| option
Before this commit, |PEG.buildParser| always returned a parser object.
The only way to get its source code was to call the |toSource| method on
it. While this method worked for parsers produced by |PEG.buildParser|
directly, it didn't work for parsers instantiated by executing their
source code. In other words, it was unreliable.

This commit remvoes the |toSource| method on generated parsers and
introduces a new |output| option to |PEG.buildParser|. It allows callers
to specify whether they want to get back the parser object
(|options.output === "parser"|) or its source code (|options.output ===
"source"|). This is much better and more reliable API.
12 years ago
David Majda 3629d880d3 Make sure the |options| param passed to passes is always an object
Pass code can be simpler as a result.
12 years ago
David Majda 0519d7e3ce Git repo npmization: Make the repo a npm package
Includes:

  * Moving the source code from /src to /lib.
  * Adding an explicit file list to package.json
  * Updating the Makefile.
  * Updating the spec and benchmark suites and their READMEs.

Part of a fix for GH-32.
12 years ago
David Majda e59f3ba338 Split the source code into several files, introduce build system
The source code is now in the src directory. The library needs to be
built using "rake", which creates the lib/peg.js file by combining the
source files.
14 years ago
David Majda 95a78892de Rename |PEG.compiler.compileParser| to |PEG.compiler.compile|
It's shorter and more consistent with |PEG.parser.parse|.
14 years ago
David Majda 5e64d09a15 Renamed some properties of the |PEG| object
1. |PEG.Compiler| -> |PEG.compiler|
2. |PEG.grammarParser| -> |PEG.parser|

This brings us closer to the desired structure of the PEG object, which
is:

  +-PEG
    |- parser
    +- compiler
       |- checks
       |- passes
       +- emitter

These are the only things (together with the |PEG.buildParser| function
and exceptions) that I want to be publicly accessible -- as extension
points and also for easy testing of PEG.js's components.
14 years ago
David Majda 1682a25b0d Move emitter utility functions out of |PEG.Compiler| 14 years ago
David Majda e5a5572a87 Factored the code emitter out of the compiler 14 years ago
David Majda 2622f432bd Move compiler checks and passes out of |PEG.Compiler| definition
This allows splitting them into separate files in the future. It also
decreases indentation level in the code.
14 years ago
David Majda d7d1a0b28c Remove unused |PEG.ArrayUtils.range| utility function 14 years ago
David Majda 98da358ef4 Found a neater trick how to defend against |undefined| redefinition 14 years ago
David Majda af1968054b Implement semantic predicates 14 years ago
David Majda 4895f4f8e4 Treat the whole grammar as an AST node 14 years ago
David Majda 917cf1cf2a Start rule of the grammar is now implicitly its first rule
Before this change, the start rule was the one named "start" and there
was an option to override that. This is now impossible.

The goal of this change is to contain all information for the parser
generation in the grammar itself.

In the future, some override directive for the start rule (like Bison's
"%start") may be added to the grammar.
14 years ago
David Majda 70cf4cd94d Reset generated variable names for each rule parsing function
Little change in the source grammar now does not change variables in all
the generated code. This is helpful especially when one has the
generated grammar stored in a VCS (this is true e.g. for our
metagrammar).
14 years ago
David Majda 66de889f4b Implement initializers 14 years ago
David Majda 718bcf5f87 Rename the |action| property of action AST nodes to |code| 14 years ago
David Majda c0f0d56975 Fix incorrect comment 14 years ago
David Majda 8a2e21fa3f Inlined the |initialContext| variable 14 years ago
David Majda 439c815e48 Move lot of stuff in generated parsers into the |parse| method
We want to have the rule parsing functions inside the |parse| method
because we want them to share a common environment. In the future,
initializers will be executed in this enviromnent and thus functions and
variables defined by them will be accessible to the rule parsing
functions.

Moving various private properties from the parser object into the
|parse| method was not strictly necessary, but it was a natural step
after moving the functions.
14 years ago
David Majda 1daf1448e5 Get rid of the |_startRule| property in generated parsers. 14 years ago
David Majda 7d4911ec53 Emit little bit less whitespace in actions 14 years ago
David Majda ee8c121676 Use labeled expressions and variables instead of $1, $2, etc.
Labeled expressions lead to more maintainable code and also will allow
certain optimizations (we can ignore results of expressions not passed
to the actions).

This does not speed up the benchmark suite execution statistically
significantly on V8.

Detailed results (benchmark suite totals):

---------------------------------
 Test #     Before       After
---------------------------------
      1   28.43 kB/s   28.46 kB/s
      2   28.38 kB/s   28.56 kB/s
      3   28.22 kB/s   28.58 kB/s
      4   28.76 kB/s   28.55 kB/s
      5   28.57 kB/s   28.48 kB/s
---------------------------------
Average   28.47 kB/s   28.53 kB/s
---------------------------------

Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.55 Safari/533.4
14 years ago
David Majda 52704593cd Allow labeled expressions in the metagrammar (without any meaning yet) 14 years ago
David Majda d1fc16c373 Fix bug causing incorrect error messages
The bug could cause the list of expected strings in an error message to
contain strings that shouldn't be there.

Closes #2.
14 years ago
David Majda 90ed4712e9 Add compiler optimization: Remove proxy rules
This shouldn't have measurable effect on the benchmarks as there are no
proxy rules in the grammars the benchamrk uses. However the effect on
generated parsers' speed should be positive generally.
14 years ago
David Majda 33a1a7c1e9 Clean up class handling in the metagrammar and compiler
The class AST node now contains structured data and a raw text which is
used for error messages.
14 years ago
David Majda 137a4b4f53 Renamed |characters| -> |chars| (shorter, no loss of expressivity) 14 years ago
David Majda 4e968892be Guard against redefinition of |undefined|
In most cases, code pattern

  x === undefined

was transformed to

  typeof(x) === "undefined"

and similarly with |!==|.

In the generated code, the condition was simply made less strict to
avoid performance penalty of string comparison (I don't think JavaScript
VMs optimize this specific pattern to avoid it).
14 years ago
David Majda d85bfcb2e9 Fix comment typo 14 years ago
David Majda 76ed63c86e AST refactoring 6/6: Get rid of the |Grammar| namespace 14 years ago
David Majda b4bf49443a AST refactoring 5/6: Make AST classless 14 years ago
David Majda 41abb7ad92 AST refactoring 4/6: Rewrite compilation to not extend the AST nodes 14 years ago
David Majda 5885a34dde AST refactoring 3/6: Rewrite checks to not extend the AST nodes 14 years ago
David Majda c59516541f AST refactoring 2/6: Add |type| property to AST nodes 14 years ago
David Majda 4e99a2bda7 AST refactoring 1/6: Make properties of AST nodes public
Removed undescore prefix of the properties and deleted getters.
14 years ago
David Majda 6f2a188efc Checks refactoring
Changed two things:

  1. Checks are run in |PEG.Compiler.compileParser|, not in
     |PEG.buildParser|.

  2. Checks are extracted into separate functions.
14 years ago
David Majda 85930cbcfe Reorder AST stuff more consistently and sensibly
There is no functional change in this commit.
14 years ago
David Majda f28c52fde2 Rename |_element| to |_expression| in AST
All nodes which have one subexpression store it in the |_expression| property
now.
14 years ago
David Majda cc7f1d96eb Avoid |call| when calling actions with one parameter.
This speeds up the benchmark suite execution by 0.18%, which may just be a
measurement error. (Standrad statistic tests would tell more, but I don't want
to mess with them now.) The code is little bit nicer this way though.

Going further and avoiding |apply| seems to slow thigs down a bit, possibly
because multiple array accesses. I may try improved version without array
accesses (where Action passes the Sequence variable names to save the results
into) sometime later.

Detailed results (benchmark suite totals):

---------------------------------
 Test #     Before       After
---------------------------------
      1   29.08 kB/s   28.91 kB/s
      2   28.72 kB/s   28.75 kB/s
      3   28.78 kB/s   28.88 kB/s
      4   28.57 kB/s   28.90 kB/s
      5   28.84 kB/s   28.81 kB/s
---------------------------------
Average   28.80 kB/s   28.85 kB/s
---------------------------------

Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.9 Safari/533.2
14 years ago
David Majda e3aa4df090 Changed action parameter processing to avoid the arguments object.
The action now computes the number of passed parameters during the code
generation and the parameters are declared directly as $1, $2, etc. in the
generated function.

This does not speed up the benchmark suite execution statistically significantly
on V8.

Detailed results (benchmark suite totals):

---------------------------------
 Test #     Before       After
---------------------------------
      1   28.68 kB/s   29.08 kB/s
      2   28.77 kB/s   28.72 kB/s
      3   28.89 kB/s   28.78 kB/s
      4   28.84 kB/s   28.57 kB/s
      5   28.86 kB/s   28.84 kB/s
---------------------------------
Average   28.81 kB/s   28.80 kB/s
---------------------------------

Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.9 Safari/533.2
14 years ago
David Majda a1dcd245bb Action AST node no longer accepts functions as actions (only strings). 14 years ago
David Majda b2f230fad2 Added Optional AST node.
This does not speed up the benchmark suite execution statistically significantly
on V8.

Detailed results (benchmark suite totals):

---------------------------------
 Test #     Before       After
---------------------------------
      1   28.84 kB/s   28.75 kB/s
      2   28.76 kB/s   28.69 kB/s
      3   28.72 kB/s   28.69 kB/s
      4   28.84 kB/s   28.93 kB/s
      5   28.82 kB/s   28.70 kB/s
---------------------------------
Average   28.80 kB/s   28.75 kB/s
---------------------------------

Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.2 (KHTML, like Gecko)
Chrome/5.0.342.9 Safari/533.2
14 years ago
David Majda e5df8284b5 Added AndPredicate AST node.
This does not speed up the benchmark suite execution statistically significantly
on V8.

Detailed results (benchmark suite totals):

---------------------------------
 Test #     Before       After
---------------------------------
      1   28.72 kB/s   28.84 kB/s
      2   28.84 kB/s   28.76 kB/s
      3   28.83 kB/s   28.72 kB/s
      4   28.81 kB/s   28.84 kB/s
      5   28.76 kB/s   28.82 kB/s
---------------------------------
Average   28.79 kB/s   28.80 kB/s
---------------------------------

Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.9 Safari/533.2
14 years ago