The C++ Generator

With no flags, px compiles a grammar straight into a C++ source file defining a table of rules for the Peg<> template (declared in peg.h, in the same repo):

px grammars/json.px > json_parser.cpp

What comes out

Each rule becomes one Rule table entry: its name, its body compiled into a compact runtime pattern language (“Pegexp”, but using <name> for a rule call, everything else is close to what you wrote), and, if it captures anything, an array of the capture names:

const char*	TOP_captures[] = { "object", "array", "string", "v", "number", 0 };

JsonParser::Rule	JsonParser::rules[] =
{
	{ "s",
	  "*[ \\t\\r\\n]",
	  0
	},
	{ "TOP",
	  "<s>(|<object>|<array>|<string>|true:v:|false:v:|null:v:|<number>)<s>",
	  TOP_captures
	},
	...
};

int	JsonParser::num_rule = sizeof(JsonParser::rules)/sizeof(JsonParser::rules[0]);

That’s just data, not code — Peg<> is what turns a Rule[] table into a parser. A program can have many different parsers without any additional code, and could in principle even create new parsers on the fly.

Declaring the parser class

The generated file expects JsonParser to already be declared, by instantiating Peg<> yourself — exactly the way px_parser.h declares PxParser for Px’s own self-hosted grammar. All JsonParser supplies is the Rule[] table and its length, to Peg<>’s own constructor:

// json_parser.h
#include <strval.h>
#include <variant.h>
#include <peg.h>
#include <peg_ast.h>

class JsonParser
: public Peg<PegMemorySource, PegMatch, PegContext>
{
	static	Rule	rules[];
	static	int	num_rule;
public:
	JsonParser() : Peg(rules, num_rule) {}
};

PegMemorySource, PegMatch and PegContext (all in peg_ast.h) are the ready-made implementation that parses into a Variant abstract syntax tree — matched text becomes a Variant::String, and each rule’s captures become entries in a Variant::StrVarMap, with repeated captures collected into a Variant::VarArray (that’s how object’s k, v capture ends up as two parallel arrays — see The Px Language).

If you need different results or more optimised implementation, you can define your own Context type to expand the Peg<> template.

Parsing something

Peg::parse() does the work, PegMatch::is_failure() says whether it worked, and on failure the Source itself (here PegMemorySource, via current_byte() and its current_line()/current_column() siblings) says how far it got:

#include <json_parser.h>
#include <json_parser.cpp>   // the generated Rule[] table
#include <cstdio>

int main()
{
	const char*     text = "{\"answer\": 42, \"ok\": true}";
	JsonParser      parser;
	JsonParser::Source source(text);

	JsonParser::Match match = parser.parse(source);
	if (match.is_failure())
	{
		printf("parse failed at byte %lld\n", (long long)source.current_byte());
		return 1;
	}

	printf("%s\n", match.var.as_json(2).asUTF8());
	return 0;
}

match.var is the parsed Variant AST — Variant::as_json() (used above with 2-space indenting) is a quick way to inspect any Variant, turned into a plain C string with StrVal::asUTF8(). That’s exactly what px -j uses to dump a grammar’s own parsed structure when you want to see how Px itself parsed a .px file, before it’s compiled further.

On failure, match.furthermost_success is how far the parser got, and match.failures lists which atoms it tried and failed at that point — px’s own error reporting (what you see when a .px file fails to parse) is built from exactly these two fields.

A grammar describing a sequence of independent items

The TOP grammar for Px defined in px.px matches just one rule at a time, although a Px grammar normally requires multiple rules. px.cpp calls the parser repeatedly, progressing through the input one rule at a time. Each parse starts from match.furthermost_success (where the previous parse reached), until the whole file is consumed. This means that the entire abstract syntax tree doesn’t need to exist at one time.