Grammar In. Bulletproof Parser Out.
Blazing fast. Runs anywhere. Streams forever. Reparses in a blink.
PEGO turns one grammar file into a production parser. The grammar language extends
Parsing Expression Grammars with types, operator
precedence, left recursion and error recovery, so a single .pego file defines both the syntax and the tree you get
back. Run it on the engine, on a portable bytecode VM, or as generated code with zero dependencies — the result is
the same everywhere.
calc.pego
Edit in the playground →
// calc.pego
type Num terminal
type Bin struct { Left Expr, Op Match, Right Expr }
type Expr = Bin | Num
def main: Expr = pratt {
skip " "*
operand num
operand "(" e:main ")" -> $e
level { infix left "+" / "-" -> new Bin{Left: $lhs, Op: $op, Right: $rhs} }
level { infix left "*" / "/" -> new Bin{Left: $lhs, Op: $op, Right: $rhs} }
level { infix right "^" -> new Bin{Left: $lhs, Op: $op, Right: $rhs} }
}
def num: Num = (?0-9)+
$ pego parse -g calc.pego -f sexpr -i '1 + 2 * 3 ^ 2'
(Bin Left=Num"1"@num Op="+" Right=(Bin Left=Num"2"@num Op="*" Right=(Bin Left=Num"3"@num Op="^" Right=Num"2"@num)))
$ pego parse -g calc.pego -i '1 + * 2'
pego: 1:5: syntax error: expected "(", (?0-9)
p, _ := pego.CompileSource(src, "main")
tree, err := p.Parse("1 + 2 * 3 ^ 2") // a *pego.Node, or a *pego.SyntaxError with line, column and expectations
Note
PEGO is under active development; the language and the Go API may still change. See the implementation status and roadmap.
Why PEGO
Edits in a blink
Document reparses only what an edit touched: about 1 ms after a one-character edit to an
89 KB program, 15× faster than parsing it again.
Infinite input, flat memory
ParseStream emits each element as soon as it matches and drops the input it
has consumed, so memory stays constant however long the stream runs.
Write once, run anywhere
Grammars compile to a language-independent bytecode with a
published specification. Precompiled .pegoc files load in tens of microseconds, and
pego gen emits a standalone parser: Go that needs nothing but the standard library, or a TypeScript module with
no dependencies for Node.js, Deno, Bun and browsers.
Fast out of the box
A 262 KB JSON document becomes a fully typed, positioned tree in about 3 ms with a generated parser — less time than merely validating it. Recognition mode validates without building a tree at all.
Every backend, one answer
Closure engine, recursive VM, iterative VM, generated Go and TypeScript: the test suite checks that all of them return identical trees, positions and errors.
No nesting too deep
The iterative VM runs on its own stack, up to 10 million nested rule calls.
Types all the way down
Declare struct, union and terminal types and build them in actions. Rule types are
inferred and checked when the grammar compiles, and pego gen -types emits matching Go types that the parser
builds directly — in half the time of its own tree and a quarter of the memory.
Precedence without the pain
Operators are pratt levels, not a ladder of rules. Left recursion, direct or
indirect, just works.
Errors that point the way
Failures report the farthest position and what was expected there. #error
rewrites the message; #recover skips the broken part and keeps going.
See what your grammar does
pego trace shows every rule call with positions, results and memo hits,
pego explain which calls make up a syntax error, and pego profile where the time and the backtracking go.
pego lint points out alternatives that can never match and other likely mistakes before any input does.
At home in your editor
pego lsp gives any LSP editor errors as you type, formatting, go to definition,
hover with inferred rule types, rename and completion; a VS Code extension is included.
Context when you need it
Predicates and scoped variables handle indentation, matching tags and other things plain PEG cannot.
#Install
go get github.com/ornew/pego # library
go install github.com/ornew/pego/cmd/pego@latest # command-line tool
Go 1.27 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 everything the edit did not touch
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 compile -g grammar.pego -o grammar.pegoc # precompile
pego gen -g grammar.pego -pkg calc -o parser.go # generate a standalone Go parser
pego gen -g grammar.pego -pkg calc -types # ... with Go types for the grammar's types
pego gen -g grammar.pego -pkg calc -recognize # ... with Recognize, which validates without a tree
pego gen -lang ts -g grammar.pego -o parser.ts # generate a standalone TypeScript module
pego sample -g grammar.pego -n 20 -coverage # generate inputs the grammar accepts (tests, fuzz seeds)
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:
import "github.com/ornew/pego/parsers/json"
v, err := json.Decode(`{"name": "pego", "tags": ["peg", "pratt"]}`)
| 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 (Pratt 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.
#License
Documentation
Getting started with PEGO
From a first grammar to typed trees and operator precedence, step by step.
PEGO Guides
Task-oriented guides to trees, expressions, errors, context, runtimes and code generation.
The PEGO Language Specification
The normative definition of the PEGO grammar language.
API Reference
The Go API and the command-line tool, generated from the source.
Examples
Small grammars that show the features: a calculator, an outline and a small language.
Design Records
The decisions behind PEGO and their rationale.