Until now, expectations were constructed using object literals. This
commit changes the construction to use factory functions.
This change makes generated parsers slightly smaller because property
names don't have to be repeated many times and factory function calls
are more amenable to minifying.
Some numbers based on the aggregate size of parsers generated from
examples/*.pegjs:
Optimization Minified? Size before Size after Saving
------------------------------------------------------------
speed no 719066 716063 0.42%
speed yes 188998 180202 4.65%
size no 194810 197813 1.52%
size yes 108782 99947 8.12%
(Minification was done using "uglify --mangle --compress" with
uglify-js 2.4.24.)
The "buildMessage" utility function, which was previously internal, is
now exposed as SyntaxError.buildMessage in generated parsers.
The motivation behind this is two-fold:
1. Building of a syntax error message is a responsibility of the
SyntaxError class, meaning the code should be placed there.
2. By exposing the message building code, parser users can use it to
generate customized error messages without duplicating PEG.js's code.
Note that helper functions inside "buildMessage" ("describeExpected",
"describeFound", etc.) currently aren't exposed. They may become exposed
in the future if there is enough demand.
Instead of pre-generating expectation descriptions when generating
parsers, generate them dynamically from structured information contained
in the expectations.
This change makes descriptions a presentation-only concept. It also
makes generated parsers smaller.
Before this commit, expectations were sorted and de-duplicated before
they were passed to "buildMessage" and exposed in the "expected"
property of syntax errors. This commit moves this processing into
"buildMessage" and rewrites it to process only expectation descriptions.
This means expectations exposed in the "expected" property are "raw"
(not sorted and de-duplicated).
This change will allow us to get rid of the "description" property of
expectations and compute descriptions dynamically from structured
information in the expectations. This will make descriptions a
presentation-only concept. It will also make generated parsers smaller.
Note that to keep expectations in the "expected" property sorted even
without the "description" property, some sorting scheme based on
structured information in the expectations would have to be devised,
which would complicate things with only a little benefit. Therefore I
chose to keep the expectations there "raw".
Changes:
* Remove the "value" property (it is replaced by other properties).
* Add the "parts", "inverted", and "ignoreCase" properties (which
allow more structured access to expectation data).
Changes:
* Rename the "value" property to "text" (because it doesn't contain
the whole value, which also includes the case sensitivity flag).
* Add the "ignoreCase" property (which was missing).
If the described class is case-sensitive, nothing changes.
If the described class is case-insensitive, its description doesn't
indicate that anymore. The indication was awkward and it was meaningful
only for parser users familiar with PEG.js grammar syntax (typically a
minority). For cases where case insensitivity indication is vital, named
rules can be used to customize the reporting.
Note that literal descriptions already ignore the case-sensitivity flag;
this commit only makes things consistent.
Simplify regexps that specify ranges of characters to escape with "\xXX"
and "\uXXXX" in various escaping functions. Until now, these regexps
were (mostly) mutually exclusive with more selective regexps applied
before them, but this became a maintenance headache. I decided to
abandon the exclusivity, which allowed to simplify these regexps (at the
cost of introducing an ordering dependency).
Change how found strings are escaped when building syntax error
messages:
* Do not escape non-ASCII characters (U+0100-U+FFFF). They are
typically more readable in their raw form.
* Escape DEL (U+007F). It is a control character.
* Escape NUL (U+0000) as "\0", not "\x00".
* Do not use less known shortcut escape sequences ("\b", "\f"), only the
well-known ones ("\0", "\t", "\n", "\r").
These changes mirror expectation escaping changes done in
4fe682794d.
Part of work on #428.
Before this commit, descriptions of literals used in error messages were
built by applying JavaScript string escaping to their values, making the
descriptions look like JavaScript strings. Descriptions of character
classes were built using their raw text. These approaches were mutually
inconsistent and lead to descriptions which were over-escaped and not
necessarily human-friendly (in case of literals) or coupled with details
of the grammar (in case of character classes).
This commit changes description building code in both cases and unifies
it. The intent is to generate human-friendly descriptions of matched
expressions which are clean, unambiguous, and which don't escape too
many characters, while handling special characters such as newlines
well.
Fixes#127.
I no longer think that using raw literal texts in error messages is the
right thing to do. The main reason is that it couples error messages
with details of the grammar such as use of single or double quotes in
literals. A better solution is coming in the next commit.
This reverts commit 69a0f769fc.
The wrapping functions are also generated by PEG.js, so the comment
should be above them to mark them as such. This shouldn't cause any
problems technically.
Introduce two ways of specifying parser dependencies: the "dependencies"
option of PEG.buildParser and the -d/--dependency CLI option. Specified
dependencies are translated into AMD dependencies and Node.js's
"require" calls when generating an UMD parser.
Part of work on #362.
Extract "generateWrapper" code which generates code of the intro and the
returned parser object into helper functions. This is pure refactoring,
generated parser code is exactly the same as before.
This change will make it easier to modifiy "generateWrapper" to produce
UMD modules.
Part of work on #362.
Code which was at the toplevel of the "generateJS" function in the code
generator is now split into "generateToplevel" (which genreates parser
toplevel code) and "generateWrapper" (which generates a wrapper around
it). This is pure refactoring, generated parser code is exactly the same
as before.
This change will make it easier to modifiy the code genreator to produce
UMD modules.
Part of work on #362.
Instead of testing arguments.length to see whether an optional parameter
was passed to a function, compare its value to "undefined". This
approach has two advantages:
* It is in line with handling of default parameters in ES6.
* Optional parameters are actually spelled out in the parameter
list.
There is also one important disadvantage, namely that it's impossible to
pass "undefined" as an optional parameter value. This required a small
change in two tests.
Additional notes:
* Default parameter values are set in assignments immediately
after the function header. This reflects the fact that these
assignments really belong to the parameter list (which is where they
are in ES6).
* Parameter values are checked against "void 0" in places where
"undefined" can potentially be redefiend.
Labels in expressions like "(a:"a")" or "(a:"a" b:"b" c:"c")" were
visible to the outside despite being wrapped in parens. This commit
makes them invisible, as they should be.
Note this required introduction of a new "group" AST node, whose purpose
is purely to provide label scope isolation. This was necessary because
"label" and "sequence" nodes don't (and can't!) provide this isolation
themselves.
Part of a fix of #396.
Before this commit, generated parsers considered the following character
sequences as newlines:
Sequence Description
------------------------------
"\n" Unix
"\r" Old Mac
"\r\n" Windows
"\u2028" line separator
"\u2029" paragraph separator
This commit limits the sequences only to "\n" and "\r\n". The reason is
that nobody uses Unicode newlines or "\r" in practice.
A positive side effect of the change is that newline-handling code
became simpler (and likely faster).
The expectation deduplication algorithm called |Array.prototype.splice|
to eliminate each individual duplication, which was slow. This caused
problems with grammar/input combinations that generated a lot of
expecations (see #377 for an example).
This commit replaces the algorithm with much faster one, eliminating the
problem.
The |found| property wasn't very useful as it mostly contained just one
character or |null| (the exception being syntax errors triggered by
|error| or |expected|). Similarly, the "but XXX found" part of the error
message (based on the |found| property) wasn't much useful and was
redundant in presence of location info.
For these reasons, this commit removes the |found| property and
corresponding part of the error message from syntax errors. It also
modifies error location info slightly to cover a range of 0 characters,
not 1 character (except when the error is triggered by |error| or
|expected|). This corresponds more precisely to the actual situation.
Fixes#372.
Report left recursion also in cases where the recursive rule invocation
is not a direct element of a sequence, but is wrapped inside an
expression.
Fixes#359.
Before this commit, the |reportLeftRecursion| pass was written in
functional style, passing the |visitedRules| array around as a parameter
and making a new copy each time a rule was visited. This apparently
caused performance problems in some deeply recursive grammars.
This commit makes it so that there is just one array which is shared
across all the visitor functions via a closure and modified as rules are
visited.
I don't like losing the functional style (it was elegant) but
performance is more important.
Fixes#203.
* In strict mode code, functions can only be declared at top level or
immediately within another function. This means functions defined in
the initializer would throw.
Before this commit, position details (line and column) weren't computed
efficiently from the current parse position. There was a cache but it
held only one item and it was rarely hit in practice. This resulted in
frequent rescanning of the whole input when the |location| function was
used in various places in a grammar.
This commit extends the cache to remember position details for any
position they were ever computed for. In case of a cache miss, the cache
is searched for a value corresponding to the nearest lower position,
which is then used to compute position info for the desired position
(which is then cached). The whole input never needs to be rescanned.
No items are ever evicted from the cache. I think this is fine as the
max number of entries is the length of the input. If this becomes a
problem I can introduce some eviction logic later.
The performance impact of this change is significant. As the benchmark
suite doesn't contain any grammar with |location| calls I just used a
little ad-hoc benchmark script which measured time to parse the grammar
of PEG.js itself (which contains |location| calls):
var fs = require("fs"),
parser = require("./lib/parser");
var grammar = fs.readFileSync("./src/parser.pegjs", "utf-8"),
startTime, endTime;
startTime = (new Date()).getTime();
parser.parse(grammar);
endTime = (new Date()).getTime();
console.log(endTime - startTime);
The measured time went from ~293 ms to ~54 ms on my machine.
Fixes#337.
Replace |line|, |column|, and |offset| properties of tracing events with
the |location| property. It contains an object similar to the one
returned by the |location| function available in action code:
{
start: { offset: 23, line: 5, column: 6 },
end: { offset: 25, line: 5, column: 8 }
}
For the |rule.match| event, |start| refers to the position at the
beginning of the matched input and |end| refers to the position after
the end of the matched input.
For |rule.enter| and |rule.fail| events, both |start| and |end| refer to
the current position at the time the rule was entered.
Replace |line|, |column|, and |offset| properties of |SyntaxError| with
the |location| property. It contains an object similar to the one
returned by the |location| function available in action code:
{
start: { offset: 23, line: 5, column: 6 },
end: { offset: 25, line: 5, column: 8 }
}
For syntax errors produced in the middle of the input, |start| refers to
the first unparsed character and |end| refers to the character behind it
(meaning the span is 1 character). This corresponds to the portion of
the input in the |found| property.
For syntax errors produced the end of the input, both |start| and |end|
refer to a character past the end of the input (meaning the span is 0
characters).
For syntax errors produced by calling |expected| or |error| functions in
action code the location info is the same as the |location| function
would return.
Preform the following renames:
* |reportedPos| -> |savedPos| (abstract machine variable)
* |peg$reportedPos| -> |peg$savedPos| (variable in generated code)
* |REPORT_SAVED_POS| -> |LOAD_SAVED_POS| (instruction)
* |REPORT_CURR_POS| -> |UPDATE_SAVED_POS| (instruction)
The idea is that the name |reportedPos| is no longer accurate after the
|location| change (seea the previous commit) because now both
|reportedPos| and |currPos| are reported to user code. Renaming to
|savedPos| resolves this inaccuracy.
There is probably some better name for the concept than quite generic
|savedPos|, but it doesn't come to me.
Replace |line|, |column|, and |offset| functions with the |location|
function. It returns an object like this:
{
start: { offset: 23, line: 5, column: 6 },
end: { offset: 25, line: 5, column: 8 }
}
In actions, |start| refers to the position at the beginning of action's
expression and |end| refers to the position after the end of action's
expression. This allows one to easily add location info e.g. to AST
nodes created in actions.
In predicates, both |start| and |end| refer to the current position.
Fixes#246.
Beside the recursion detector, the visitor will also be used by infinite
loop detector.
Note the newly created |asts.matchesEmpty| function re-creates the
visitor each time it is called, which makes it slower than necessary.
This could have been worked around in various ways but I chose to defer
that optimization because real-world performance impact is small.
So far, left recursion detector assumed that left recursion occurs only
when the recursive rule is at the very left-hand side of rule's
expression:
start = start
This didn't catch cases like this:
start = "a"? start
In general, if a rule reference can be reached without consuming any
input, it can lead to left recursion. This commit fixes the detector to
consider that.
Fixes#190.
Parsers can now be generated with support for tracing using the --trace
CLI option or a boolean |trace| option to |PEG.buildParser|. This makes
them trace their progress, which can be useful for debugging. Parsers
generated with tracing support are called "tracing parsers".
When a tracing parser executes, by default it traces the rules it enters
and exits by writing messages to the console. For example, a parser
built from this grammar:
start = a / b
a = "a"
b = "b"
will write this to the console when parsing input "b":
1:1 rule.enter start
1:1 rule.enter a
1:1 rule.fail a
1:1 rule.enter b
1:2 rule.match b
1:2 rule.match start
You can customize tracing by passing a custom *tracer* to parser's
|parse| method using the |tracer| option:
parser.parse(input, { trace: tracer });
This will replace the built-in default tracer (which writes to the
console) by the tracer you supplied.
The tracer must be an object with a |trace| method. This method is
called each time a tracing event happens. It takes one argument which is
an object describing the tracing event.
Currently, three events are supported:
* rule.enter -- triggered when a rule is entered
* rule.match -- triggered when a rule matches successfully
* rule.fail -- triggered when a rule fails to match
These events are triggered in nested pairs -- for each rule.enter event
there is a matching rule.match or rule.fail event.
The event object passed as an argument to |trace| contains these
properties:
* type -- event type
* rule -- name of the rule the event is related to
* offset -- parse position at the time of the event
* line -- line at the time of the event
* column -- column at the time of the event
* result -- rule's match result (only for rule.match event)
The whole tracing API is somewhat experimental (which is why it isn't
documented properly yet) and I expect it will evolve over time as
experience is gained.
The default tracer is also somewhat bare-bones. I hope that PEG.js user
community will develop more sophisticated tracers over time and I'll be
able to integrate their best ideas into the default tracer.
Rename |generateCache{Header,Footer}| to |generateRule{Header,Footer}|
and change their responsibility to generate overall header/footer of a
rule function (when optimizing for speed) or the |peg$parseRule|
function (when optimizing for speed). This creates a natural place where
to generate tracing code (coming soon).
Action and predicate code can now see variables defined in expressions
"above" them.
Based on a pull request by Bryon Vandiver (@asterick):
https://github.com/pegjs/pegjs/pull/180Fixes#316.
The |visitor.build| function now supplies default visit functions for
visitors it builds. These functions don't do anything beside traversing
the tree and passing arguments around to child visit functions.
Having the default visit functions allowed to simplify several visitors.
The TEXT instruction now replaces position at the top of the stack with
the input from that position until the current position. This is simpler
and cleaner semantics than the previous one, where TEXT also popped an
additional value from the stack and kept the position there.
Implement the following bytecode instructions:
* PUSH_UNDEFINED
* PUSH_NULL
* PUSH_FAILED
* PUSH_EMPTY_ARRAY
These instructions push simple JavaSccript values to the stack directly,
without going through constants. This makes the bytecode slightly
shorter and the bytecode generator somewhat simpler.
Also note that PUSH_EMPTY_ARRAY allows us to avoid a hack which protects
the [] constant from modification.
The |stringEscape| function both in lib/compiler/javascript.js and in
generated parsers didn't escape characters in the U+0100..U+107F and
U+1000..U+107F ranges.
Split lib/utils.js into multiple files. Some of the functions were
generic, these were moved into files in lib/utils. Other funtions were
specific for the compiler, these were moved to files in lib/compiler.
This commit only moves functions around -- there is no renaming and
cleanup performed. Both will come later.
Modules now generally store the exported object in a named variable or
function first and only assign |module.exports| at the very end. This is
a difference when compared to style used until now, where most modules
started with a |module.exports| assignment.
I think the explicit name helps readability and understandability.
Initializer code is usually indented and this indentation is carried
over to generated code. This resulted in a piece of indented code in the
middle of the parser.
This commit wraps initializer code in |{...}|, which makes indentation
in generated parsers look a bit more natural.
The action/predicate code didn't have access to the parser object. This
was mostly a side effect actions/predicates being implemented as nested
functions, in which |this| is a reference to the global object (an ugly
JavaScript quirk). The initializer, being implemented differently, had
access to the parser object via |this|, but this was not documented.
Because having access to the parser object can be useful, this commits
introduces a new |parser| variable which holds a reference to it, is
visible in action/predicate/initializer code, and is properly
documented.
See also:
https://groups.google.com/forum/#!topic/pegjs/Na7YWnz6Bmg
Fixes the following JSHint error:
lib/compiler/passes/generate-bytecode.js: line 334, col 71, Expected an assignment or function call and instead saw an expression.
The one-parameter |Array.prototype.splice| call is a SpiderMonkey
extension. Apparently, IE doesn't implement it (unlike other supported
browsers), so we need to replace it with two-parameter version.
In case the generated parser parsed successfully part of input and left
some input unparsed (trailing input), the error message produced was
sometimes wrong. The code worked correctly only if there were no match
failures in the successfully parsed part (highly unlikely).
This commit fixes things by explicitly triggering a match failure with the
following expectation at the end of the successfully parsed part of the
input:
peg$fail({ type: "end", description: "end of input" });
This change also made it possible to simplify the |buildMessage|
function, which can now ignore the case of no expectations.
Fixes#119.
There are two invariants in generated bytecode related to the stack:
1. Branches of a condition must move the stack pointer in the same way.
2. Body of a loop can't move the stack pointer.
These invariants were always true, but they were not checked. Now we
check them at least when compiling with optimization for speed, because
there we analyze the stack pointer movements statically.
The error check was useful when actions could have returned |null| to
trigger a match failure. This is no longer supported so the check isn't
needed anymore.
Speed impact
------------
Before: 1022.70 kB/s
After: 1035.45 kB/s
Difference: 1.24%
Size impact
-----------
Before: 975434 b
After: 931540 b
Difference: -4.50%
(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)