Documentation Index
Fetch the complete documentation index at: https://mintlify.com/saiashirwad/parserator/llms.txt
Use this file to discover all available pages before exploring further.
What is Generator Syntax?
Generator syntax provides an imperative-style way to write parsers using JavaScript/TypeScript generator functions. Instead of chaining methods, you use yield* to sequence parsers, making complex parsing logic more readable.
The parser() Function
The parser() function (alias: Parser.gen()) creates a parser from a generator function:
const parser: <T>(f: () => Generator<Parser<any>, T, any>) => Parser<T>
Basic Usage
Simple Sequential Parsing
import { parser, char, identifier, number } from 'parserator';
const keyValue = parser(function* () {
const key = yield* identifier();
yield* char(':');
const value = yield* number();
return { key, value };
});
keyValue.parse("age:30"); // { key: "age", value: 30 }
Discarding Results
Use yield* without assignment when you need to match something but don’t need its value:
const email = parser(function* () {
const username = yield* many1(or(alphabet, digit, char(".")));
yield* char("@"); // Required but value discarded
const domain = yield* many1(or(alphabet, digit)).map(chars => chars.join(""));
yield* char("."); // Required but value discarded
const tld = yield* many1(alphabet).map(chars => chars.join(""));
return {
username: username.join(""),
domain: domain + "." + tld
};
});
vs Method Chaining
The same parser can be written using method chaining or generator syntax:
Method Chaining
const coordinate = number()
.zip(char(',').then(number()))
.map(([x, y]) => ({ x, y }));
Generator Syntax
const coordinate = parser(function* () {
const x = yield* number();
yield* char(',');
const y = yield* number();
return { x, y };
});
Conditional Parsing
Generator syntax makes conditional logic natural:
const value = parser(function* () {
const first = yield* or(
string("true").map(() => "bool" as const),
string("null").map(() => "null" as const),
char('"').map(() => "string" as const),
char('[').map(() => "array" as const),
char('{').map(() => "object" as const),
regex(/-?[0-9]/).map(() => "number" as const)
);
switch (first) {
case "bool":
return yield* or(
string("true").map(() => true),
string("false").map(() => false)
);
case "null":
return null;
case "string":
return yield* jsonString;
case "array":
return yield* jsonArray;
case "object":
return yield* jsonObject;
case "number":
return yield* jsonNumber;
}
});
Loops
You can use regular loops to repeatedly parse:
const jsonString = parser(function* () {
yield* char('"');
const chars: string[] = [];
while (true) {
const next = yield* or(
string('\\"').map(() => '"'),
string('\\\\').map(() => '\\'),
regex(/[^"\\]+/),
char('"').map(() => null) // End marker
);
if (next === null) break;
chars.push(next);
}
return chars.join("");
});
Error Propagation
Errors automatically propagate through generator syntax:
const ifStatement = parser(function* () {
yield* keyword("if");
yield* char('(').expect("opening parenthesis"); // Error if missing
const condition = yield* expression; // Won't run if previous failed
yield* char(')').expect("closing parenthesis");
const body = yield* block;
return { type: "if", condition, body };
});
If any yield* expression fails, the entire parser fails immediately and subsequent yields are not executed.
Committing in Generators
Use commit() to prevent backtracking after identifying the construct:
import { parser, commit, keyword, char } from 'parserator';
const ifStatement = parser(function* () {
yield* keyword("if");
yield* commit(); // No backtracking after this point
yield* char('(').expect("opening parenthesis after 'if'");
const condition = yield* expression;
yield* char(')').expect("closing parenthesis");
const body = yield* block;
return { type: "if", condition, body };
});
Real-World Example: JSON Parser
Here’s a complete JSON object parser using generator syntax:
import { parser, char, string, sepBy, token } from 'parserator';
const jsonObject: Parser<Record<string, any>> = parser(function* () {
yield* token(char("{"));
const pairs = yield* sepBy(
parser(function* () {
const key = yield* token(jsonString);
yield* token(char(":"));
const value = yield* token(jsonValue);
return [key, value] as const;
}),
token(char(","))
);
yield* token(char("}"));
return Object.fromEntries(pairs);
});
When to Use Generator Syntax
Use generator syntax when:
- You have sequential parsing with multiple intermediate values
- Your parsing logic involves conditionals or loops
- You find imperative code more readable than method chains
- You need to perform transformations on intermediate results
Use method chaining when:
- You’re building simple linear parsers
- You want to emphasize the functional composition
- The parser is short and straightforward
Type Safety
Generator syntax is fully type-safe. TypeScript infers the return type automatically:
const parser = parser(function* () {
const name = yield* identifier(); // string
yield* char(':');
const age = yield* number(); // number
return { name, age }; // Inferred as { name: string, age: number }
});
// parser has type Parser<{ name: string, age: number }>
Generator syntax has no performance penalty compared to method chaining. Both compile to the same underlying parser operations.