In the bytecode generator, the |context.action| property wasn't
correctly reset when generating bytecode for sequence elements. As a
result, when a sequence was wrapped in an action and it contained
another sequence as an element, the generator thought that the inner
sequence was wrapped in an action too.
For example, the following grammar:
start = ("a" "b") "c" { return "x"; }
was compiled as if it looked like this:
start = ("a" "b" { return "x"; }) "c" { return "x"; }
This commit fixes the problem by resetting |context.action| correctly.
Fixes GH-168.
Code that calculated which part of the input to match against a literal
was wrong in case of case-insensitive literals when generating
speed-optimized parsers. As a result, matching of case-insensitive
literals worked only at the end of the input (where too big length
passed to the |substr| method didn't matter).
Fixes GH-153.
The deduplication skipped over an expected string right after the one
that was removed because the index variable was incorrectly incremented
in that case.
Based on a patch by @fresheneesz:
https://github.com/dmajda/pegjs/pull/146
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.
The |plugins| option allows users to use plugins that change how PEG.js
operates.
A plugin is any JavaScript object with a |use| method. After the user
calls |PEG.buildParser|, this method is called for each plugin with the
following two parameters:
* PEG.js config that describes used grammar parser and compiler
passes used to generate the parser
* options passed by user to |PEG.buildParser|
The plugin is expected to change the config as needed, possibly based on
the options passed by user. It can e.g. change the used grammar parser,
change the compiler passes (including adding its own), etc. This way it
can extend PEG.js in a flexible way.
Implements part of GH-106.
Fix automatic semi-colon insertion in var statements without
initialisers.
var i
i = 1;
is valid and not accepted by the parser
but
var i = 2
i = 3;
is valid and accepted by the parser, as it should be.
With this fix, both are accepted.
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.
Fixes the following JSHint errors (which I think are JSHint bugs):
spec/parser.spec.js: line 142, col 20, Bad for in variable 'key'.
spec/generated-parser.spec.js: line 119, col 20, Bad for in variable 'key'.
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 :-)
Previously, the report-left-recursion and report-missing-rules passes
used PEG.GrammarError without requiring it, causing a ReferenceError.
Since requiring lib/peg.js would cause circular requirements, this
commit imports lib/grammar-error.js as GrammarError.
The bug was introduced in commit
4cda79951a.
Fixes GH-135.
When called inside an action, the |text| function returns the text
matched by action's expression. It can be also called inside an
initializer or a predicate where it returns an empty string.
The |text| function will be useful mainly in cases where one needs a
structured representation of the input and simultaneously the raw text.
Until now, the only way to get the raw text in these cases was to
painfully build it from the structured representation.
Fixes GH-131.
Implement a new syntax to extract matched strings from expressions. For
example, instead of:
identifier = first:[a-zA-Z_] rest:[a-zA-Z0-9_]* { return first + rest.join(""); }
you can now just write:
identifier = $([a-zA-Z_] [a-zA-Z0-9_]*)
This is useful mostly for "lexical" rules at the bottom of many
grammars.
Note that structured match results are still built for the expressions
prefixed by "$", they are just ignored. I plan to optimize this later
(sometime after the code generator rewrite).
Cache the last reported position info. If the position advances, the
code uses the cache and only computes the differnece. If the position
goes back, the cache is simply dropped.
Getting rid of the |trackLineAndColumn| simplifies the code generator
(by unifying two paths in the code).
The |line| and |column| functions currently always compute all the
position info from scratch, which is horribly ineffective. This will be
improved in later commit(s).
This will allow to compute position data lazily and get rid of the
|trackLineAndColumn| option without affecting performance of generated
parsers that don't use position data.