da57118a43
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.
209 lines
6.6 KiB
JavaScript
209 lines
6.6 KiB
JavaScript
describe("PEG.js API", function() {
|
|
describe("buildParser", function() {
|
|
it("builds a parser", function() {
|
|
var parser = PEG.buildParser('start = "a"');
|
|
|
|
expect(typeof parser).toBe("object");
|
|
expect(parser.parse("a")).toBe("a");
|
|
});
|
|
|
|
it("throws an exception on syntax error", function() {
|
|
expect(function() { PEG.buildParser('start = @'); }).toThrow();
|
|
});
|
|
|
|
it("throws an exception on semantic error", function() {
|
|
expect(function() { PEG.buildParser('start = missing'); }).toThrow();
|
|
});
|
|
|
|
describe("allowed start rules", function() {
|
|
var grammar = [
|
|
'a = "x"',
|
|
'b = "x"',
|
|
'c = "x"'
|
|
].join("\n");
|
|
|
|
/*
|
|
* The |allowedStartRules| option is implemented separately for each
|
|
* optimization mode, so we need to test it in both.
|
|
*/
|
|
|
|
describe("when optimizing for parsing speed", function() {
|
|
describe("when |allowedStartRules| is not set", function() {
|
|
it("generated parser can start only from the first rule", function() {
|
|
var parser = PEG.buildParser(grammar, { optimize: "speed" });
|
|
|
|
expect(parser.parse("x", { startRule: "a" })).toBe("x");
|
|
expect(
|
|
function() { parser.parse("x", { startRule: "b" }); }
|
|
).toThrow();
|
|
expect(
|
|
function() { parser.parse("x", { startRule: "c" }); }
|
|
).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("when |allowedStartRules| is set", function() {
|
|
it("generated parser can start only from specified rules", function() {
|
|
var parser = PEG.buildParser(grammar, {
|
|
optimize: "speed",
|
|
allowedStartRules: ["b", "c"]
|
|
});
|
|
|
|
expect(
|
|
function() { parser.parse("x", { startRule: "a" }); }
|
|
).toThrow();
|
|
expect(parser.parse("x", { startRule: "b" })).toBe("x");
|
|
expect(parser.parse("x", { startRule: "c" })).toBe("x");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when optimizing for code size", function() {
|
|
describe("when |allowedStartRules| is not set", function() {
|
|
it("generated parser can start only from the first rule", function() {
|
|
var parser = PEG.buildParser(grammar, { optimize: "size" });
|
|
|
|
expect(parser.parse("x", { startRule: "a" })).toBe("x");
|
|
expect(
|
|
function() { parser.parse("x", { startRule: "b" }); }
|
|
).toThrow();
|
|
expect(
|
|
function() { parser.parse("x", { startRule: "c" }); }
|
|
).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("when |allowedStartRules| is set", function() {
|
|
it("generated parser can start only from specified rules", function() {
|
|
var parser = PEG.buildParser(grammar, {
|
|
optimize: "size",
|
|
allowedStartRules: ["b", "c"]
|
|
});
|
|
|
|
expect(
|
|
function() { parser.parse("x", { startRule: "a" }); }
|
|
).toThrow();
|
|
expect(parser.parse("x", { startRule: "b" })).toBe("x");
|
|
expect(parser.parse("x", { startRule: "c" })).toBe("x");
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("intermediate results caching", function() {
|
|
var grammar = [
|
|
'{ var n = 0; }',
|
|
'start = (a "b") / (a "c") { return n; }',
|
|
'a = "a" { n++; }'
|
|
].join("\n");
|
|
|
|
describe("when |cache| is not set", function() {
|
|
it("generated parser doesn't cache intermediate parse results", function() {
|
|
var parser = PEG.buildParser(grammar);
|
|
|
|
expect(parser.parse("ac")).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("when |cache| is set to |false|", function() {
|
|
it("generated parser doesn't cache intermediate parse results", function() {
|
|
var parser = PEG.buildParser(grammar, { cache: false });
|
|
|
|
expect(parser.parse("ac")).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("when |cache| is set to |true|", function() {
|
|
it("generated parser caches intermediate parse results", function() {
|
|
var parser = PEG.buildParser(grammar, { cache: true });
|
|
|
|
expect(parser.parse("ac")).toBe(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("tracing", function() {
|
|
var grammar = 'start = "a"';
|
|
|
|
describe("when |trace| is not set", function() {
|
|
it("generated parser doesn't trace", function() {
|
|
var parser = PEG.buildParser(grammar);
|
|
|
|
spyOn(console, "log");
|
|
|
|
parser.parse("a");
|
|
|
|
expect(console.log).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("when |trace| is set to |false|", function() {
|
|
it("generated parser doesn't trace", function() {
|
|
var parser = PEG.buildParser(grammar, { trace: false });
|
|
|
|
spyOn(console, "log");
|
|
|
|
parser.parse("a");
|
|
|
|
expect(console.log).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("when |trace| is set to |true|", function() {
|
|
it("generated parser traces", function() {
|
|
var parser = PEG.buildParser(grammar, { trace: true });
|
|
|
|
spyOn(console, "log");
|
|
|
|
parser.parse("a");
|
|
|
|
expect(console.log).toHaveBeenCalledWith("1:1 rule.enter start");
|
|
expect(console.log).toHaveBeenCalledWith("1:2 rule.match start");
|
|
});
|
|
});
|
|
});
|
|
|
|
/*
|
|
* The |optimize| option isn't tested because there is no meaningful way to
|
|
* write the specs without turning this into a performance test.
|
|
*/
|
|
|
|
describe("output", function() {
|
|
var grammar = 'start = "a"';
|
|
|
|
describe("when |output| is not set", function() {
|
|
it("returns generated parser object", function() {
|
|
var parser = PEG.buildParser(grammar);
|
|
|
|
expect(typeof parser).toBe("object");
|
|
expect(parser.parse("a")).toBe("a");
|
|
});
|
|
});
|
|
|
|
describe("when |output| is set to |\"parser\"|", function() {
|
|
it("returns generated parser object", function() {
|
|
var parser = PEG.buildParser(grammar, { output: "parser" });
|
|
|
|
expect(typeof parser).toBe("object");
|
|
expect(parser.parse("a")).toBe("a");
|
|
});
|
|
});
|
|
|
|
describe("when |output| is set to |\"source\"|", function() {
|
|
it("returns generated parser source code", function() {
|
|
var source = PEG.buildParser(grammar, { output: "source" });
|
|
|
|
expect(typeof source).toBe("string");
|
|
expect(eval(source).parse("a")).toBe("a");
|
|
});
|
|
});
|
|
});
|
|
|
|
/* The |plugins| option is tested in plugin API specs. */
|
|
|
|
it("accepts custom options", function() {
|
|
PEG.buildParser('start = "a"', { foo: 42 });
|
|
});
|
|
});
|
|
});
|