pegjs/spec/api/plugin-api.spec.js
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.
2015-03-30 14:00:19 +02:00

158 lines
4.7 KiB
JavaScript

describe("plugin API", function() {
beforeEach(function() {
this.addMatchers({
toBeObject: function() {
this.message = function() {
return "Expected " + jasmine.pp(this.actual) + " "
+ (this.isNot ? "not " : "")
+ "to be an object.";
};
return this.actual !== null && typeof this.actual === "object";
},
toBeArray: function() {
this.message = function() {
return "Expected " + jasmine.pp(this.actual) + " "
+ (this.isNot ? "not " : "")
+ "to be an array.";
};
return Object.prototype.toString.apply(this.actual) === "[object Array]";
},
toBeFunction: function() {
this.message = function() {
return "Expected " + jasmine.pp(this.actual) + " "
+ (this.isNot ? "not " : "")
+ "to be a function.";
};
return typeof this.actual === "function";
},
});
});
describe("use", function() {
var grammar = 'start = "a"';
it("is called for each plugin", function() {
var pluginsUsed = [false, false, false],
plugins = [
{ use: function() { pluginsUsed[0] = true; } },
{ use: function() { pluginsUsed[1] = true; } },
{ use: function() { pluginsUsed[2] = true; } }
];
PEG.buildParser(grammar, { plugins: plugins });
expect(pluginsUsed).toEqual([true, true, true]);
});
it("receives configuration", function() {
var plugin = {
use: function(config, options) {
var i;
expect(config).toBeObject();
expect(config.parser).toBeObject();
expect(config.parser.parse('start = "a"')).toBeObject();
expect(config.passes).toBeObject();
expect(config.passes.check).toBeArray();
for (i = 0; i < config.passes.check.length; i++) {
expect(config.passes.check[i]).toBeFunction();
}
expect(config.passes.transform).toBeArray();
for (i = 0; i < config.passes.transform.length; i++) {
expect(config.passes.transform[i]).toBeFunction();
}
expect(config.passes.generate).toBeArray();
for (i = 0; i < config.passes.generate.length; i++) {
expect(config.passes.generate[i]).toBeFunction();
}
}
};
PEG.buildParser(grammar, { plugins: [plugin] });
});
it("receives options", function() {
var buildParserOptions = { foo: 42 },
plugin = {
use: function(config, options) {
expect(options).toEqual(buildParserOptions);
}
};
PEG.buildParser(grammar, buildParserOptions);
});
it("can replace parser", function() {
var plugin = {
use: function(config, options) {
var parser = PEG.buildParser([
'start = .* {',
' return {',
' type: "grammar",',
' rules: [',
' {',
' type: "rule",',
' name: "start",',
' expression: { type: "literal", value: text(), ignoreCase: false }',
' }',
' ]',
' };',
'}'
].join("\n"));
config.parser = parser;
}
},
parser = PEG.buildParser('a', { plugins: [plugin] });
expect(parser.parse("a")).toBe("a");
});
it("can change compiler passes", function() {
var plugin = {
use: function(config, options) {
var pass = function(ast) {
ast.code = '({ parse: function() { return 42; } })';
};
config.passes.generate = [pass];
}
},
parser = PEG.buildParser(grammar, { plugins: [plugin] });
expect(parser.parse("a")).toBe(42);
});
it("can change options", function() {
var grammar = [
'a = "x"',
'b = "x"',
'c = "x"'
].join("\n"),
plugin = {
use: function(config, options) {
options.allowedStartRules = ["b", "c"];
}
},
parser = PEG.buildParser(grammar, {
allowedStartRules: ["a"],
plugins: [plugin]
});
expect(function() { parser.parse("x", { startRule: "a" }); }).toThrow();
expect(parser.parse("x", { startRule: "b" })).toBe("x");
expect(parser.parse("x", { startRule: "c" })).toBe("x");
});
});
});