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).
Instead of setting ESLint environment to "node" globally, set it on
per-directory basis using separate .eslintrc.json files:
Directory Environment
-----------------------
bin node
lib commonjs
spec jasmine
It was impossible to use this approach for the "benchmark" directory
which contains a mix of files used in various environments. For
benchmark/run, the environment is set inline. For the other files, as
well as spec/helpers.js, the globals are declared manually (it is
impossible to express how these files are used just by a list of
environments).
Fixes#408.
Fix the following errors:
31:9 error "parser" is defined but never used no-unused-vars
406:14 error "expected" is defined but never used no-unused-vars
1304:15 error "s1" is defined but never used no-unused-vars
1386:15 error "s1" is defined but never used no-unused-vars
1442:15 error "s1" is defined but never used no-unused-vars
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.
In the past year I worked on various grammars where first/rest or
head/tail were used as labels for parts of lists. I found I associate
head/tail with a list immediately, while in case of first/rest I have to
"parse" grammar rules for a while before understanding their structure.
Moreover, I tend to assume that rest is a list of the same thigs as
first, but I don't have such assumption in case of head/tail. This
assumption was in conflict with the grammar structure.
I'm not sure how much these observations are applicable to others, but I
decided to act on them and switch from first/rest to head/tail.
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.
Add missing |named| case to the visitor in lib/compiler/asts.js, which
makes the infinite loop and left recursion detectors work correctly with
named rules.
The missing case caused |make parser| to fail with:
140:34: Infinite loop detected.
make: *** [parser] Error 1
* 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
This is mostly done for consistency with the JavaScript example grammar,
from which the |Identifier| rule is taken from. See the previous commit
for details.
Instead of matching segments between blocks character by character,
match them as a whole. Also align the style with other similar rules
(e.g. the comment ones).
Before this commit, line continuations in character classes contributed
an empty string to the list of characters and character ranges matched
by a class. While this didn't lead to a buggy behavior with the current
code generator, the AST was wrong and the problem could have caused bugs
later.
This commit fixes the problem.
Semantic predicates are kind of |PrimaryExpression|, not kind of
|PrefixedExpression|. Therefore I extracted a rule for them and
referenced it from the |PrimaryExpression|.
Initializer and rules are now separated in a similar way as JavaScript
statements -- either by a semicolon or a line terminator, possibly with
whitespace and comments mixed in.
One consequence is that the grammars like this are now illegal:
foo = "a" bar = "b"
A semicolon needs to be inserted between the rules:
foo = "a";bar = "b"
I consider this a good change as the now-illegal syntax was somewhat
confusing.
This makes the |Primary| rule a bit more tidy. Also, matching the |.|
character really belongs to the lexical part of the grammar, next to
literals and character classes.
* Rename the |Action| rule to |CodeBlock| (it better describes what
the rule matches).
* Implement the rule in a simpler way and move it after more basic
lexical elements.
This change has two side effects:
* Label names can no longer be JavaScript reserved words.
* |$| is allowed again in label names. However, because of the
preference rules, names starting with it will be usually parsed as a
text operator followed by another identifier (denoting a rule
reference or label name).
Before this commit, whitespace was handled at the lexical level by
making tokens consume any whitespace coming after them. This was
accomplished by appending |__| to every token rule.
This commit changes whitespace handling to be more explicit. Tokens no
longer consume whitespace coming after them and syntactic rules have to
cope with it. While this slightly complicates the syntactic grammar, I
think it's a cleaner way. Moreover, it is what JavaScript example
grammar does.
One small side-effect of thich change is that the grammar is now
stand-alone (it doesn't require utils.js anymore).
When rule names are capitalized, it's easier to visually distinguish
them from non-capitalized label names. Moreover, I use capitalized rule
names in all my grammars these days.
Before this commit, a line continuation (backslash followed by a line
terminator character) contributed a character to a string or a character
class it was used in. In JavaScript and many other languages, line
continuation doesn't contribute anything.
This commit aligns PEG.js line continuation behavior with JavaScript.
Before this commit, the value of the |rawText| property of "class" AST
nodes was created in a hackish way from processed input and it didn't
always exactly represent the actual input text.
This commit changes the code so that the value of the |rawText| property
is created using the |text| function. This is a clean way which also
resolves the exact representation problem.
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.