PEGO

One Grammar. Every Parser.

Never write a parser again.

Describe your language once. PEGO builds the parser: fast, safe and wherever you need it. Here is a complete calculator, operator precedence included:

Get started Open the playground

Try it: calc.pego Edit in the playground →
// calc.pego
type Num = node[string]
type Bin = { Left Expr; Op node[string]; Right Expr }
type Expr ~ Bin | Num

@start
rule main Expr = #operators {
    skip " "*
    operand num
    operand "(" e:main ")" => $e
    level additive { infix left "+" / "-" => Bin{Left: $lhs, Op: $op, Right: $rhs} }
    level multiplicative { infix left "*" / "/" => Bin{Left: $lhs, Op: $op, Right: $rhs} }
    level power { infix right "^" => Bin{Left: $lhs, Op: $op, Right: $rhs} }
}

rule num Num = ([0-9]+) #atomic
Tree loading the parser…

  
pego parse -g calc.pego -f sexpr -i '1 + 2 * 3 ^ 2'
Bin{Left: Num("1"), Op: "+", Right: Bin{Left: Num("2"), Op: "*", Right: Bin{Left: Num("3"), Op: "^", Right: Num("2")}}}
pego parse -g calc.pego -i '1 + * 2'
pego: 1:5: syntax error: expected "(", [0-9]

Note

PEGO is pre-release: the language, the Go API and the file formats may change. Generated Go is not yet as fast as Go's hand-written parsers, and closing that gap is the current work. Why PEGO has every measured number, the current limits and how PEGO compares with other tools.

Why PEGO

Runs anywhere, any way

Any target language, any engine, any optimization. One grammar becomes generated code, a VM or an interpreter, tuned for speed, memory or size, and every one of them returns the same tree, the same positions and the same errors. Whatever you build, PEGO fits.

Unmatched expressive power

Build values of any type you define, carry state for context-sensitive syntax and write a whole operator precedence table as one rule. Recover from errors with a complete tree, and parse endless streams as they arrive. If you can describe a language, you can parse it.

Performance pushed to the limit

A multi-stage IR optimizes at every level, from grammar-wide rewrites down to the generated code. Incremental parsing reuses everything an edit did not touch, so a fresh tree is ready between keystrokes.

Safe on any input

PEGO analyzes your grammar to bound the time and memory a parse can take, and built-in limits on work and nesting mean hostile input can never hang the parser or blow its stack.

Complete support for building your language

A built-in language server brings completion, diagnostics, navigation and formatting to any editor. Trace, profile, lint and debug show exactly what the parser does, so you never have to guess.

Install

go get github.com/ornew/pego                        # library
go install github.com/ornew/pego/cmd/pego@latest    # command-line tool

Go 1.27.1 or later. No dependencies outside the standard library.

Use It

From Go

p, err := pego.CompileSource(grammarSource, "main") // grammar errors come with positions

node, err := p.Parse(input)                       // positions in code points by default
p.Parse(input, pego.WithUnit(pego.Bytes))         // ... or in UTF-8 bytes
p.Parse(input, pego.WithBackend(pego.Bytecode))   // pick a backend
p.Parse(input, pego.RecognizeOnly())              // validate without building a tree
json.Marshal(node)                                // nodes encode with types and positions

err = p.ParseStream(r, func(n *pego.Node) error { return nil }) // one #stream element at a time

doc, _ := p.NewDocument(text)
doc.Edit(10, 12, "new text") // replace [10, 12)
node, err = doc.Parse()      // reuses rule results the edit did not reach

data, _ := p.MarshalBinary() // save; pego.LoadParser(data) skips parsing and checking

From the command line

pego parse -g grammar.pego -i 'abc=12'           # parse and print the tree (JSON, or -f sexpr)
pego parse -g grammar.pego -check < input.txt    # validate only
pego explain -g grammar.pego < input.txt         # which rule calls make up a syntax error
pego trace -g grammar.pego -i 'abc=12'           # every rule call as an indented tree (-f json for tools)
pego profile -g grammar.pego < input.txt         # cost per rule, with hints on wasted work
pego lint -g grammar.pego                        # likely mistakes: dead alternatives, unused captures, ...
pego lsp                                         # language server for editors (errors, formatting, rename, ...)
pego fmt -w grammar.pego                         # format in place, comments kept
pego fix -w grammar.pego                         # rewrite older syntax to the current one
pego compile -g grammar.pego -o grammar.pegoc    # precompile
pego gen -g grammar.pego -module example.com/calc -o calc  # generate a standalone Go parser
pego gen -g grammar.pego -module example.com/calc -types -o calc # ... with Go types for the grammar's types
pego gen -g grammar.pego -module example.com/calc -recognize -o calc # ... with Recognize, which validates without a tree
pego gen -lang ts -g grammar.pego -o ts-parser    # generate a TypeScript parser into a directory
pego gen -lang ts -g grammar.pego -types -o ts-parser # also emit types.ts and parseAST
pego gen -g grammar.pego -module example.com/calc -max-depth 600000 -o calc # set its nested rule-call default
pego sample -g grammar.pego -n 20 -coverage      # generate inputs the grammar accepts (tests, fuzz seeds)
pego edit-fuzz -g grammar.pego -minimize -save-failures out  # check Document edits against fresh parses

Run pego without arguments for every command and flag.

Ready-Made Parsers

parsers/ has parsers for common languages, written in PEGO, generated into Go and tested against the languages' conformance suites and reference implementations. Each is a module of its own that depends only on the standard library and PEGO's runtime package. Until PEGO publishes a release, use them from a checkout of the repository; go get does not fetch them from another module yet (see Ready-Made Parsers):

import "github.com/ornew/pego/parsers/json"

v, err := json.Decode(`{"name": "pego", "tags": ["peg", "operators-rule"]}`)
Language Module Specification
CEL github.com/ornew/pego/parsers/cel The CEL language definition
CSV github.com/ornew/pego/parsers/csv RFC 4180
CUE github.com/ornew/pego/parsers/cue The CUE language specification
DuckDB SQL github.com/ornew/pego/parsers/duckdb DuckDB 1.5.6's SQL dialect
Go github.com/ornew/pego/parsers/golang The Go specification (go1.27), as go/parser parses it
JSON github.com/ornew/pego/parsers/json RFC 8259
Python github.com/ornew/pego/parsers/python Python 3.14
TypeScript github.com/ornew/pego/parsers/typescript TypeScript 5.9 and TSX
XML github.com/ornew/pego/parsers/xml XML 1.0 (Fifth Edition), non-validating
YAML github.com/ornew/pego/parsers/yaml YAML 1.2.2

Examples

Small grammars with tests that show the features live in examples/: a calculator (Operators Rule and left-recursive), an indentation-based outline format and a small programming language. The complete parsers of parsers/, such as the ones for Go and Python 3, are the larger examples.

Contributing

Bug reports, ideas and pull requests are welcome. Run go test ./... before sending changes, and follow the commit message guidelines. Report security vulnerabilities privately, as the security policy describes.

License

Apache License 2.0

Documentation