/* global peg */

"use strict";

describe("PEG.js API", function() {
  describe("generate", function() {
    it("generates a parser", function() {
      var parser = peg.generate('start = "a"');

      expect(typeof parser).toBe("object");
      expect(parser.parse("a")).toBe("a");
    });

    it("throws an exception on syntax error", function() {
      expect(function() { peg.generate('start = @'); }).toThrow();
    });

    it("throws an exception on semantic error", function() {
      expect(function() { peg.generate('start = undefined'); }).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.generate(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.generate(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.generate(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.generate(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.generate(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.generate(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.generate(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.generate(grammar),
              tracer = jasmine.createSpyObj("tracer", ["trace"]);

          parser.parse("a", { tracer: tracer });

          expect(tracer.trace).not.toHaveBeenCalled();
        });
      });

      describe("when |trace| is set to |false|", function() {
        it("generated parser doesn't trace", function() {
          var parser = peg.generate(grammar, { trace: false }),
              tracer = jasmine.createSpyObj("tracer", ["trace"]);

          parser.parse("a", { tracer: tracer });

          expect(tracer.trace).not.toHaveBeenCalled();
        });
      });

      describe("when |trace| is set to |true|", function() {
        it("generated parser traces", function() {
          var parser = peg.generate(grammar, { trace: true }),
              tracer = jasmine.createSpyObj("tracer", ["trace"]);

          parser.parse("a", { tracer: tracer });

          expect(tracer.trace).toHaveBeenCalled();
        });
      });
    });

    /*
     * 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.generate(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.generate(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.generate(grammar, { output: "source" });

          expect(typeof source).toBe("string");
          expect(eval(source).parse("a")).toBe("a");
        });
      });
    });

    /*
     * The |format|, |exportVars|, and |dependencies| options are not tested
     * becasue there is no meaningful way to thest their effects without turning
     * this into an integration test.
     */

    /* The |plugins| option is tested in plugin API specs. */

    it("accepts custom options", function() {
      peg.generate('start = "a"', { foo: 42 });
    });
  });
});
