60 Commits (20a4fb2e7f70a0695bee4aef4984b24c06db3627)

Author SHA1 Message Date
David Majda 20a4fb2e7f Update version to 0.9.0 9 years ago
David Majda 671c22e80f Avoid using |console| in default tracer and its tests when not defined
This makes default tracer and its tests work in IE 8-10.
9 years ago
David Majda 6d82422045 Remove trailing comma in an array literal
It caused an additional newline in generated parsers in IE 8.
9 years ago
David Majda 5e6b5da4e9 Merge pull request #347 from mbaumgartl/errorstack
Add stack trace in engines based on V8
9 years ago
Marco Baumgartl 940a66fb38 Add stack trace in engines based on V8. Fixes #331 9 years ago
Arlo Breault 12c169e7b5 Convert PEG.js code to strict mode
* Issues #323
9 years ago
Arlo Breault 45e39c3ac8 Make generated parsers use strict mode
* Issue #324

 * JSHint complains about two possible strict violations. But are valid
   uses of `this`, so we suppress the warnings.
9 years ago
Arlo Breault 7285ccfd4e Remove block around initialize code
* 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.
9 years ago
Arlo Breault 7695e5e3c5 Fix complaints from `make hint` 9 years ago
David Majda f2200e48af Optimize location info computation
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.
9 years ago
David Majda 29bb921994 Rename |peg$cache| to |peg$resultsCache|
This change will make the results cache clearly distinguishable from the
position details cache (which I'll add in a minute).
9 years ago
David Majda d1fe86683b Improve location info in tracing events
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.
9 years ago
David Majda 065f4e1b75 Improve location info in syntax errors
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.
9 years ago
David Majda b1ad2a1f61 Rename |reportedPos| to |savedPos|
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.
9 years ago
David Majda 4f7145e360 Improve location info available in action code
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.
9 years ago
David Majda da57118a43 Implement basic support for tracing
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.
9 years ago
David Majda 675561f085 Rename and generalize |generateCache{Header,Footer}|
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).
9 years ago
David Majda fb7de36051 Update website URL
PEG.js website was moved from http://pegjs.majda.cz/ to http://pegjs.org/.
10 years ago
David Majda 24394e3f91 Fix comment alignment in lib/compiler/passes/generate-javascript.js 10 years ago
David Majda dad1207c46 Improve semantics of the TEXT bytecode instruction
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.
10 years ago
David Majda a815a8b902 Implement additional PUSH_* bytecode instructions
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.
10 years ago
David Majda 85c8f386c1 Formatting 10 years ago
David Majda f03ba4bf4f generate-javascript.js: s/generateJavaScript/generateJavascript/
This makes the variable name in sync with pass name in lib/compiler.js.
10 years ago
David Majda 57f7fae684 Fix a bug in |stringEscape|
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.
10 years ago
David Majda 88e5f136e1 Utility functions cleanup: Cleanup lib/compiler/javascript.js 10 years ago
David Majda bfaad70899 Utility functions cleanup: Cleanup lib/compiler/asts.js 10 years ago
David Majda 5adad3ae12 Utility functions cleanup: Split lib/utils.js
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.
10 years ago
David Majda ff8e877fce Change module exporting style
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.
10 years ago
David Majda d9354c4632 Standardize on 3 spaces before // comments 10 years ago
David Majda f3a83788aa Inline functions extracted just because of JSHint
Rather than extracting functions just because JSHint complained about
defining functions inside a loop, let's inline then and silence the
warning.
10 years ago
David Majda 46ac1bf171 Wrap initializer code in generated parsers into |{...}|
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.
10 years ago
David Majda 39084496ca Expose the parser object in action/predicate code
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
10 years ago
David Majda c7521fb868 Mark |parse| and |SyntaxError| as internal identifiers
The |parse| function and the |SyntaxError| exception were meant as
internal, so let's mark them as such.
10 years ago
David Majda 2263a30034 Update version to 0.8.0 11 years ago
David Majda 06a83448df Remove various unused variables and function parameters 11 years ago
David Majda 976328b7d6 Avoid |Array.prototype.splice| call with one parameter
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.
11 years ago
David Majda a56d3ac94f Fix error messages in certain cases with trailing input
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.
11 years ago
David Majda 44e03187a7 Assert that generated bytecode manipulates stack correctly
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.
11 years ago
David Majda 187f9d6bb0 Remove the |NIP_CURR_POS| bytecode instruction
After the previous commit is is not used anywhere.
11 years ago
David Majda 2f2152204a Refine error handling further
Before this commit, the |expected| and |error| functions didn't halt the
parsing immediately, but triggered a regular match failure. After they
were called, the parser could backtrack, try another branches, and only
if no other branch succeeded, it triggered an exception with information
possibly based on parameters passed to the |expected| or |error|
function (this depended on positions where failures in other branches
have occurred).

While nice in theory, this solution didn't work well in practice. There
were at least two problems:

  1. Action expression could have easily triggered a match failure later
     in the input than the action itself. This resulted in the
     action-triggered failure to be shadowed by the expression-triggered
     one.

     Consider the following example:

       integer = digits:[0-9]+ {
         var result = parseInt(digits.join(""), 10);

         if (result % 2 === 0) {
           error("The number must be an odd integer.");
           return;
         }

         return result;
       }

     Given input "2", the |[0-9]+| expression would record a match
     failure at position 1 (an unsuccessful attempt to parse yet another
     digit after "2"). However, a failure triggered by the |error| call
     would occur at position 0.

     This problem could have been solved by silencing match failures in
     action expressions, but that would lead to severe performance
     problems (yes, I tried and measured). Other possible solutions are
     hacks which I didn't want to introduce into PEG.js.

  2. Triggering a match failure in action code could have lead to
     unexpected backtracking.

     Consider the following example:

       class = "[" (charRange / char)* "]"

       charRange = begin:char "-" end:char {
         if (begin.data.charCodeAt(0) > end.data.charCodeAt(0)) {
           error("Invalid character range: " + begin + "-" + end + ".");
         }

         // ...
       }

       char = [a-zA-Z0-9_\-]

     Given input "[b-a]", the |charRange| rule would fail, but the
     parser would try the |char| rule and succeed repeatedly, resulting
     in "b-a" being parsed as a sequence of three |char|'s, which it is
     not.

     This problem could have been solved by using negative predicates,
     but that would complicate the grammar and still wouldn't get rid of
     unintuitive behavior.

Given these problems I decided to change the semantics of the |expected|
and |error| functions. They don't interact with regular match failure
mechanism anymore, but they cause and immediate parse failure by
throwing an exception. I think this is more intuitive behavior with less
harmful side effects.

The disadvantage of the new approach is that one can't backtrack from an
action-triggered error. I don't see this as a big deal as I think this
will be rarely needed and one can always use a semantic predicate as a
workaround.

Speed impact
------------
Before:     993.84 kB/s
After:      998.05 kB/s
Difference: 0.42%

Size impact
-----------
Before:     1019968 b
After:      975434 b
Difference: -4.37%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
11 years ago
David Majda 5460a881af Error handling: Implement the |error| function
The |error| function allows users to report custom match failures inside
actions.

If the |error| function is called, and the reported match failure turns
out to be the cause of a parse error, the error message reported by the
parser will be exactly the one specified in the |error| call.

Implements part of #198.

Speed impact
------------
Before:     999.83 kB/s
After:      1000.84 kB/s
Difference: 0.10%

Size impact
-----------
Before:     1017212 b
After:      1019968 b
Difference: 0.27%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
11 years ago
David Majda dd74ea4144 Error handling: Build error message out of |SyntaxError|'s constructor
It will be possible to create errors with user-supplied messages soon.
The |SyntaxError| class needs to be ready for that.

Implements part of #198.
11 years ago
David Majda 3fe6aba7e2 Error handling: Extract exception building into its own function
The exception-creating code will get somewhat hairy soon, so let's make
sure them mess will be contained.

Implements part of #198.
11 years ago
David Majda d96eb317fd Error handling: Rename |peg$fail| to |peg$expected|
This is in anticipation of |peg$error|. The |peg$expected| and
|peg$error| internal functions will nicely mirror the |expected| and
|error| functions available to user code in actions.

Implements part of #198.
11 years ago
David Majda af701dcf80 Error handling: Implement the |expected| function
The |expected| function allows users to report regular match failures
inside actions.

If the |expected| function is called, and the reported match failure
turns out to be the cause of a parse error, the error message reported
by the parser will be in the usual "Expected ... but found ..." format
with the description specified in the |expected| call used as part of
the message.

Implements part of #198.

Speed impact
------------
Before:     1146.82 kB/s
After:      1031.25 kB/s
Difference: -10.08%

Size impact
-----------
Before:     950817 b
After:      973269 b
Difference: 2.36%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
11 years ago
David Majda 57e806383c Error handling: Use a special value (not |null|) to indicate failure
Using a special value to indicate match failure instead of |null| allows
actions to return |null| as a regular value. This simplifies e.g. the
JSON parser.

Note the special value is internal and intentionally undocumented. This
means that there is currently no official way how to trigger a match
failure from an action. This is a temporary state which will be fixed
soon.

The negative performance impact (see below) is probably caused by
changing lot of comparisons against |null| (which likely check the value
against a fixed constant representing |null| in the interpreter) to
comparisons against the special value (which likely check the value
against another value in the interpreter).

Implements part of #198.

Speed impact
------------
Before:     1146.82 kB/s
After:      1031.25 kB/s
Difference: -10.08%

Size impact
-----------
Before:     950817 b
After:      973269 b
Difference: 2.36%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
11 years ago
David Majda 435bb8f2df Error handling: Structured expectations
Before this commit, the |expected| property of an exception object
thrown when a generated parser encountered an error contained
expectations as strings. These strings were in a human-readable format
suitable for displaying in the UI but not suitable for machine
processing. For example, expected string literals included quotes and a
string "any character" was used when any character was expected.

This commit makes expectations structured objects. This makes the
machine processing easier, while still allowing to generate a
human-readable representation if needed.

Implements part of #198.

Speed impact
------------
Before:     1180.41 kB/s
After:      1165.31 kB/s
Difference: -1.28%

Size impact
-----------
Before:     863523 b
After:      950817 b
Difference: 10.10%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)
11 years ago
David Majda 5312e124cd Fix object literal formatting in generated code 11 years ago
David Majda 74636638d0 Merge pull request #196 from vrana/comma
Add whitespace to generated action calls
11 years ago
Jakub Vrana beb557d7d3 Add whitespace to generated action calls
Avoids implicit array to string conversion.
11 years ago