pegjs-template
TypeScript icon, indicating that this package has built-in type declarations

1.4.0 • Public • Published

pegjs-template

Build PEG.js grammars using template strings.

PEG.js grammars are normally just strings; semantic actions are specified using JavaScript embedded in the string. This library allows you to write semantic actions using real JavaScript functions. This has several advantages:

  • Syntax highlighting works.
  • Code completion and other code intelligence features work.
  • Semantic actions can be written in TypeScript, allowing them to be type checked.
  • Semantic actions can easily reference variables or APIs defined outside the grammar, without any awkward workarounds like passing them in via the context.

In addition, pegjs-template allows you to write partial grammars. These are small grammars that can be merged together to make a complete grammar. This allows you to make your grammars modular and factor out shared code so that it can be reused in multiple grammars.

pegjs-template is a better way to write PEG.js grammars!

Basic usage

Use the pegGrammar function exported by this library as a template tag function and interpolate your semantic actions in as functions. If you're using the library from TypeScript, you'll need to provide a type parameter for pegGrammar; this is the type of the AST that your parser returns.

Semantic action functions must have this type:

type Action = (context: ActionContext, ...labels: any[]) => any;

The first argument to your semantic action will be an ActionContext object containing the standard PEG.js helpers:

export type PEGActionContext = {
  /** @returns the text matched by the current rule. */
  text(): string;

  /** @returns the source range matched by the current rule. */
  location(): SourceLocation;

  /** Throw an exception indicating that 'expected' was expected but not found. */
  expected(expected: string, location?: SourceLocation): never;

  /** Throw an exception with the error message 'message'. */
  error(message: string, location?: SourceLocation): never;

  /** Options passed to the parser. */
  options: Record<string, any>;
};

The remaining arguments are the semantic values for the labeled expressions in the current rule. Note that, while the context argument can be named anything, the label argument names must match the label names in the grammar exactly!

One limitation to be aware of: this library parses the argument list of your semantic action functions using a simple regular expression. Don't try to get fancy with destructuring or rest parameters in the argument list; they won't work the way you expect.

An example's worth a thousand words, so here's the arithmetic grammar example from the PEG.js docs, rewritten to use this library:

import { pegGrammar } from 'pegjs-template';

const parser = pegGrammar<number>`
// Simple Arithmetics Grammar
// ==========================
//
// Accepts expressions like "2 * (3 + 4)" and computes their value.

Expression
  = head:Term tail:(_ ("+" / "-") _ Term)* ${(_, head, tail) => {
      return tail.reduce(function(result, element) {
        if (element[1] === "+") { return result + element[3]; }
        if (element[1] === "-") { return result - element[3]; }
      }, head);
    }}

Term
  = head:Factor tail:(_ ("*" / "/") _ Factor)* ${(_, head, tail) => {
      return tail.reduce(function(result, element) {
        if (element[1] === "*") { return result * element[3]; }
        if (element[1] === "/") { return result / element[3]; }
      }, head);
    }}

Factor
  = "(" _ expr:Expression _ ")" ${(_, expr) => expr}
  / Integer

Integer "integer"
  = _ [0-9]+ ${(ctx) => parseInt(ctx.text(), 10)}

_ "whitespace"
  = [ \t\n\r]*
`;

parser.parse('2 * (3 + 4)');  // Returns '14'.

Partial grammars

You can create a partial grammar using the pegPartialGrammar function exported by this library. It's also a template tag function that works just like pegGrammar, except that instead of returning a parser it returns a PartialGrammar object.

You can interpolate PartialGrammar objects into other grammars using either pegGrammar or pegPartialGrammar. The result is just like you interpolated the source code into the grammar as text, except that any semantic action functions you defined in JavaScript come along for the ride. This makes it easy to combine smaller grammars into larger grammars while retaining all the benefits of defining your semantic actions in JavaScript.

Generate options

pegGrammar automatically calls PEG.js's generate() function for you to generate a parser from your grammar. This means that you can't provide options to generate() in the usual way. Normally the defaults are fine, but if you do need to provide options - for example, to enable tracing - you can use pegGenerateOptions(). This function accepts the same options argument that generate() does. It returns a GenerateOptions object that you can interpolate into your grammar using pegGrammar or pegPartialGrammar. This doesn't change the grammar source code itself, but the options will be recognized and passed to generate() as you'd expect.

There is one limitation: you can only interpolate one GenerateOptions object into your grammar. pegGrammar will throw an exception if you try to include more than one.

Package Sidebar

Install

npm i pegjs-template

Weekly Downloads

0

Version

1.4.0

License

MIT

Unpacked Size

17.6 kB

Total Files

9

Last publish

Collaborators

  • sethfowler