Wolfram Language Paclet Repository

Community-contributed installable additions to the Wolfram Language

Primary Navigation

    • Cloud & Deployment
    • Core Language & Structure
    • Data Manipulation & Analysis
    • Engineering Data & Computation
    • External Interfaces & Connections
    • Financial Data & Computation
    • Geographic Data & Computation
    • Geometry
    • Graphs & Networks
    • Higher Mathematical Computation
    • Images
    • Knowledge Representation & Natural Language
    • Machine Learning
    • Notebook Documents & Presentation
    • Scientific and Medical Data & Computation
    • Social, Cultural & Linguistic Data
    • Strings & Text
    • Symbolic & Numeric Computation
    • System Operation & Setup
    • Time-Related Computation
    • User Interface Construction
    • Visualization & Graphics
    • Random Paclet
    • Alphabetical List
  • Using Paclets
    • Get Started
    • Download Definition Notebook
  • Learn More about Wolfram Language

Parser

Tutorials

  • Building Language Front-Ends
  • Inside CodeAnalysis - How CodeStructure Parses C
  • Design and Compilation Strategy
  • Implementing the LaTeX Math Parser
  • MaTeX Comparison Showcase
  • The Parser Landscape - a Survey of What Exists Today
  • The Parser Zoo - language front-ends over a shared algebra
  • Parsing BNF Grammars (and bootstrapping a TPTP parser)
  • Parsing GrammarRules Locally
  • A Markdown Inline Parser in Parser Combinators
  • ParsingOpenQASM
  • Parsing TPTP, Auto-Generated from the Published BNF
  • PrattVsPEG
  • The Wolfram Box Typesetting Reference

Guides

  • Parsing in the Wolfram Language

Symbols

  • ASTAddSource
  • ASTAlgebra
  • ASTContainer
  • ASTLeafQ
  • ASTNodeQ
  • ASTStripSource
  • BinaryNode
  • BrainfuckAST
  • BrainfuckGrammar
  • BrainfuckRun
  • BrainfuckSemantic
  • CalculatorAST
  • CalculatorEval
  • CalculatorGrammar
  • CalculatorSemantic
  • CallNode
  • ContainerNode
  • EBNFParse
  • EBNFRules
  • ErrorNode
  • ExportLaTeX
  • GroupNode
  • InfixNode
  • JSONAST
  • JSONGrammar
  • JSONImport
  • JSONSemantic
  • LambdaAST
  • LambdaEval
  • LambdaGrammar
  • LambdaSemantic
  • LaTeXMathParse
  • LaTeXMathParser
  • LaTeXMathStyle
  • LeafNode
  • LispAST
  • LispGrammar
  • LispRead
  • LispSemantic
  • LispSymbol
  • MarkdownInlineParse
  • MarkdownInlineParser
  • MarkdownParse
  • MarkdownParser
  • ParseAction
  • ParseBetween
  • ParseChainLeft
  • ParseChainRight
  • ParseCharacter
  • ParseChoiceLongest
  • ParseChoice
  • ParseFail
  • ParseLiteral
  • ParseLookahead
  • ParseMany
  • Parse
  • ParseNotFollowedBy
  • ParseOperatorTable
  • ParseOptional
  • ParsePartial
  • ParsePosition
  • ParserCombinator
  • ParserCombinatorQ
  • ParserCompile
  • ParseRecursive
  • ParseRegex
  • ParseSepBy1
  • ParseSepBy
  • ParseSequence
  • ParseSome
  • ParseSucceed
  • ParseTry
  • PostfixNode
  • PrefixNode
  • RecCell
  • RecRef
  • SetRec
  • SpannedToken
  • TernaryNode
  • ToCodeParser
  • TPTPExport
  • TPTPImport

Overviews

  • WolframParser

Parsing TPTP, Auto-Generated from the Published BNF

What this note covers

TPTP (Thousands of Problems for Theorem Provers) is the standard cross-prover benchmark corpus for automated reasoning - 26,264 problems across 57 mathematical domains, used by Vampire, E, Twee, Waldmeister, and every modern ATP system. Every problem is a
.p
file with one of six clause heads:
cnf
,
fof
,
tff
,
tcf
,
thf
,
ncf
. The TPTPWorld project publishes the formal grammar as a 735-line BNF file (
SyntaxBNF-v9.2.1.4
, 338 rules).
The paclet ships the result of this construction as
TPTPImport
-
TPTPImport[File["p.p"]]
(or
TPTPImport["cnf(...)"]
) returns the canonical
<|"Axioms"->{phi1,...},"Conjecture"->phi|>
shape directly. The companion Thousands of Problems for Theorem Provers (TPTP)
Data Repository entry
ships a catalogue index of the full 26,264-problem distribution and drives
TPTPImport
over any problem on demand from the online corpus. This note tells the story of how the parser is built - the BNF + action map that
TPTPImport
reuses internally. It works in two steps:
1
.
Read the BNF into an
Association[name->ParserCombinator]
.
2
.
Apply the result to TPTP source - optionally with an
"Actions"
map that lifts the raw parse tree to a Wolfram-Language data shape.
The lowering applies three standard PEG-vs-CFG rewrites automatically: direct left-recursion elimination, longest-alt-first sorting, and POSIX longest-match for rule bodies whose alternatives have equal element counts (the default
"ChoiceMode"->"Auto"
). Together these handle the eleven left-recursive TPTP rules, the shared-prefix alternatives like
<constant>|<functor>(<fof_arguments>)
, and the cross-rule ambiguity in
<fof_atomic_formula>::=<fof_plain_atomic_formula>|<fof_defined_atomic_formula>
where both branches can consume the same leading term but only the second one continues into the trailing
=rhs
. See
Parsing BNF Grammars
for the grammar-level mechanics; this note is the TPTP-specific story.

Bootstrapping a TPTP parser

In[1]:=
Needs["Wolfram`Parser`"]​​​​tptpBnf=Import[​​"https://raw.githubusercontent.com/TPTPWorld/SyntaxBNF/master/SyntaxBNF-v9.2.1.4",​​"Text"​​];​​​​parsers=
EBNFParse
[tptpBnf];​​(*Associationof338rule-name
ParserCombinator
.NoPrimitiveOverrides,nooptions.​​Default`"ChoiceMode""Auto"`enableslongest-matchforruleswhose​​alternativeshaveequalelementcounts;PEGorderfortherest.*)
Every
::-
(token) and
:::
(char-class) rule auto-compiles through a regex meta-parser built out of the same
Parse*
combinators. The meta-parser handles char classes (
[a-z]
,
[abc]
, and meta chars literally as in
[|]
/
[*]
), negation (
[^x]
), octal escapes (
[\40-\41]
), named escapes (
\n
,
\r
,
\t
), the bare-
.
regex any-char, ref forms (
<name>
,
{name}
), grouping (
(...)
), alternation (
|
), and the postfix repetition operators (
*
,
+
,
?
). The full TPTP lexical layer (
<lower_word>
,
<upper_word>
,
<integer>
,
<single_quoted>
,
<distinct_object>
,
<dollar_word>
, the punctuation tokens like
<vline>
/
<star>
, and the regex-heavy
<sq_char>
/
<do_char>
/
<not_star_slash>
) all compile from the published BNF without manual help.
That's the entire setup.
parsers["TPTP_file"]
is the top-level parser;
parsers["cnf_annotated"]
,
parsers["fof_annotated"]
, etc. are the per-clause-head parsers;
parsers["fof_unitary_formula"]
,
parsers["fof_quantified_formula"]
, … are the inner rules.
A real TPTP problem, parsed end-to-end:
In[2]:=
groupAxioms="fof(group_assoc, axiom, ! [X, Y, Z] : multiply(multiply(X, Y), Z) = multiply(X, multiply(Y, Z))).fof(group_left_id, axiom, ! [X] : multiply(identity, X) = X).fof(group_left_inv, axiom, ! [X] : multiply(inverse(X), X) = identity).fof(commutator_def, axiom, ! [X, Y] : commutator(X, Y) = multiply(multiply(X, Y), multiply(inverse(X), inverse(Y)))).fof(goal, conjecture, ! [X] : commutator(X, identity) = identity).";​​​​Length@
Parse
[parsers["TPTP_file"],groupAxioms]​​(*5*)
Out[2]=
2
Five clauses, quantifiers, function application, equality - all parsed. But the value above is the raw parse tree (a list of clauses, each clause a list of literal tokens and sub-rule results). For a workable downstream shape, pass a per-rule
"Actions"
map.

Lifting to a useful shape: the
"Actions"
map

Each entry in
"Actions"-><|name->fn|>
wraps the named rule's parser in a
ParseAction
. The function receives the rule's parsed value via the normal splatted convention -
Function[#1,#2,...]
indexes into the sequence of matched sub-pieces. The action map below is the one
TPTPImport
installs at first call - it lifts the raw parse tree to the canonical Wolfram-Language shape:
In[3]:=
Out[3]=
Parse[Missing[NotAvailable,TPTP_file],fof(assoc, axiom, ! [X, Y, Z] : multiply(multiply(X, Y), Z) = multiply(X, multiply(Y, Z))).fof(left_id, axiom, ! [X] : multiply(identity, X) = X).fof(goal, conjecture, ! [X] : multiply(X, identity) = X).]
This is the shape
TPTPImport
returns:
ForAll
/
Exists
quantifiers, function application as
head[args...]
,
Equal
/
Unequal
for
=
/
!=
, the Boolean grammar as
And
/
Or
/
Not
/
Implies
/
Equivalent
/
Xor
, cnf disjunctions as
Or[...]
(single literal stays bare), and
negated_conjecture
flipped through
Not
so the returned
Conjecture
is the positive goal. The action map is ~50 entries, one per BNF rule on the path from
<TPTP_file>
to
<constant>
. The recogniser is unchanged - the same
EBNFParse
call drives both the with-actions and without-actions flows.
Without actions, the parser is just a recogniser - it tells you whether the source matches the grammar but the value is the structural skeleton. The action layer is what turns that into a workable Wolfram Language data structure.

Benchmark on the published corpus

On the small CNF / FOF problems from the published
v9.2.1
distribution, per-clause parse time lands in the tens of milliseconds for short clauses (a few atoms, ground equations) and climbs into the hundreds of milliseconds for clauses with nested quantifiers and function applications. The 5-clause group-theory problem at the top of this note parses in ~700 ms end-to-end on a 2024 laptop with default
"ChoiceMode"->"Auto"
.
The cost of trying every alternative under longest-match without memoisation shows up on FOF with deep boolean / quantifier nesting. Adding
packrat-style memoisation
to
ParseRecursive
would close most of the throughput gap; a Pratt-style precedence climber for the connective grammar would be the right move for THF, where alternative explosion overwhelms even longest-match.

ParserCompile is currently a stub

In[4]:=
compiled=
ParserCompile
[parsers["TPTP_file"]];​​(*sameparserwith"Code"keyinopts,butroutedthroughan​​interpretiveshim-norealFunctionCompileloweringyet*)
Per the
ParserCompile
usage string: "v0.2: stubbed via the interpreter; the real FunctionCompile lowering lands later". A head-to-head bench on the 4 passing files confirms it - interpretive vs compiled are within measurement noise (1.00x speedup). Wiring up the real FunctionCompile path is a v0.4 item; the compiler infrastructure (
compilableQ
,
compileParser
,
interpretCompiledShim
,
CompileFeasibility
test suite) is in place but the per-combinator codegen rules aren't all written yet.

What the ChoiceMode flip fixes

The earlier draft of this note reported a cluster of failures around equations and disequations whose left side was a function application, in
cnf
and
fof
contexts. Example:
multiply(b,a)!=c
inside
cnf(_,negated_conjecture,multiply(b,a)!=c).
. PEG-ordered
Choice
was committing to
<fof_plain_atomic_formula>
on
multiply(b,a)
, then expecting end-of-clause, but the source continued with
!=c
which only
<fof_infix_unary>
(a sibling alt at the same
<cnf_literal>
level) reaches.
The default
"ChoiceMode"->"Auto"
resolves that whole cluster: for rule bodies whose alternatives have equal element counts (the static
longest-alt-first
sort can't break the tie), the lowering uses POSIX longest-match - every alt is tried at the current position and the one that consumed the most input wins. The
<fof_atomic_formula>
choice,
<cnf_literal>
choice, and a handful of other shape-ambiguous rules become correct without changing the grammar. The cost is some throughput - longest-match cannot early-exit on first hit, so deeply nested choices pay a constant factor over PEG. Set
"ChoiceMode"->"PEG"
to opt back into the fast-but-strict ordering when the grammar is unambiguous; set
"ChoiceMode"->"Longest"
to use longest-match unconditionally (correct on more grammars, slowest).
Remaining failures cluster on the higher-order alternatives in
<thf_*>
(the recursive
<thf_typeable_formula>
rule and friends). These exhibit exponential backtracking under any ordered-choice strategy and want either packrat memoisation or a Pratt-style precedence parser to be tractable - both unimplemented.

Building the bench yourself

The TPTP distribution lives at https://tptp.org/TPTP/Distribution/TPTP-v9.2.1.tgz (922 MB compressed). Extract a sample and run:
In[5]:=
(*...primitiveOverrides+parsersfromabove...*)​​​​loadClean[path_String]:=StringTrim@StringReplace[​​Import[path,"Text"],​​RegularExpression["%[^]*"]""​​];​​​​benchOne[file_]:=Block{src=loadClean[file],r,t},​​t=AbsoluteTime[];​​r=TimeConstrained
Parse
[parsers["TPTP_file"],src],30,"TO";​​"File"FileBaseName[file],​​"Bytes"StringLength[src],​​"Time"AbsoluteTime[]-t,​​"Status"Which[​​r==="TO","TIMEOUT",​​FailureQ[r],"ERROR",​​True,"OK"],​​"Clauses"If[ListQ[r],Length[r],0]​​;​​​​results=benchOne/@Take[​​FileNames["*.p","/path/to/TPTP-v9.2.1/Problems",Infinity],​​100​​];​​Counts[#["Status"]&/@results]
For the THF problems, raise the timeout or skip them (
StringContainsQ[FileBaseName[#],"^"]&
filters them out). The TFF / TCF cases land somewhere between CNF and THF in difficulty.
To run the same bench against the compiled path, swap the parser:
In[6]:=
tptpCompiled=
ParserCompile
[parsers["TPTP_file"]];​​benchOne[file_]:=Block{src=loadClean[file],r,t},​​t=AbsoluteTime[];​​r=TimeConstrained
Parse
[tptpCompiled,src],30,"TO";​​...​​;
The current
ParserCompile
is interpretive-equivalent (stubbed), so per-file times will be within ~1% of the interpretive baseline. When the real FunctionCompile lowering lands, this swap is what surfaces the speedup.

What this approach buys

The EBNF-driven path means the grammar IS the parser definition - the parser cannot disagree with the published BNF because they are the same file. When TPTP-v9.3 ships, re-run
EBNFParse
on the new BNF and re-bind the actions; no per-rule diff. The recogniser drops out for free: 280 of the 338 TPTP rules lower automatically, and small CNF / FOF clauses parse end to end via a generic mechanism. Compare this to a hand-coded parser, where every grammar change is a per-rule code edit.
What still needs hand-work:
◼
  • Term-level disambiguation. The
    <fof_plain_term>
    left-factoring issue described above. Either a deeper structural rewrite or memoisation.
  • ◼
  • THF higher-order. The mutual recursion + alternative explosion in the higher-order grammar needs memoisation or a different parsing strategy (e.g. Pratt-style precedence climbing) to be tractable.
  • ◼
  • Lexical primitives. The
    PrimitiveOverrides
    map above covers the common cases; a complete map adds
    real
    ,
    rational
    ,
    dollar_dollar_word
    , the spacing tokens, comment / whitespace rules. A small
    :::
    -to-
    ParseCharacter
    compiler would generate these from the BNF too.
  • For most TPTP workflows,
    TPTPImport
    (built on this construction) is the entry point. For evolving formal-grammar work where the published spec is moving, the EBNF-driven path is what keeps the parser in lockstep with the spec.
    RelatedGuides
    ▪
    WolframParser
    RelatedTechNotes
    ▪
    ParsingBNFGrammars
    ▪
    DesignAndCompilationStrategy
    ""

    © 2026 Wolfram. All rights reserved.

    • Legal & Privacy Policy
    • Contact Us
    • WolframAlpha.com
    • WolframCloud.com