Add SANY as a parser backend option - #275
Conversation
52f40c1 to
3e23955
Compare
c8e3e79 to
18fa2a7
Compare
619578d to
5589b39
Compare
|
@muenchnerkindl @damiendoligez pleased to say this PR is now basically feature-complete and merge-ready. There are some insurmountable shortfalls on the SANY side still but they are relatively minor. |
5589b39 to
d060416
Compare
|
Does TLAPS use the JDK bundled with Isabelle to run SANY, or must users install Java separately? If a separate Java installation is required, would it make sense to distribute an ahead-of-time-compiled version of SANY instead (compare tlaplus/rfcs#16 (comment))? |
d060416 to
0fe2f91
Compare
|
We could possibly use the bundled JDK version although currently it just shells out to |
c5a7c92 to
fc376de
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds an experimental SANY-based parser backend (selectable via --parser SANY) by shelling out to SANY’s XMLExporter, parsing the emitted XML, and translating it into TLAPM’s existing internal syntax tree format. It also adds CLI/config plumbing for the new backend, includes support for adding extra module jars to SANY’s classpath, and introduces equivalence tests comparing TLAPM vs SANY parse trees.
Changes:
- Add SANY XML parsing + AST conversion pipeline (
src/sany/xml.ml,src/sany/sany.ml/.mli) and expose parse-tree S-expression export. - Add CLI/config options for selecting parser backend and providing additional module jars (
--parser,--module-jar), plus wiring intlapm_lib. - Add SANY/TLAPM parse-tree equivalence tests + CI job coverage, and fetch/install
tla2tools.jaras a dune site backend.
Reviewed changes
Copilot reviewed 55 out of 55 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/ci.yml |
Adds a CI job that runs a subset of examples through the SANY backend. |
deps/tla2tools.jar/Makefile |
Downloads tla2tools.jar for SANY XMLExporter usage. |
deps/tla2tools.jar/dune |
Adds a dune rule + site install for tla2tools.jar. |
dune-project |
Adds xmlm dependency needed by the XML parser. |
src/dune |
Links xmlm into the main library. |
src/params.ml |
Adds parser_backend and module_jar_paths params. |
src/params.mli |
Exposes new params and parser-backend type. |
src/paths.ml |
Adds classpath construction helper for locating backend jars. |
src/paths.mli |
Exposes backend_classpath_string. |
src/sany/sany.ml |
Implements conversion from SANY XML AST to TLAPM AST and S-expression export. |
src/sany/sany.mli |
Exposes SANY parse entrypoint and S-expression export. |
src/sany/translate_syntax_tree.ml |
Adjusts module opens for syntax-tree-to-sexp translation. |
src/sany/xml.ml |
Implements SANY invocation, XML parsing, and typed XML AST. |
src/tlapm_args.ml |
Adds --parser and --module-jar CLI flags. |
src/tlapm_lib.ml |
Wires SANY backend into main and modctx_of_string. |
test/cli/cli_tests.ml |
Adds CLI parsing test coverage for --module-jar. |
test/parser/parser_tests.ml |
Switches parse-tree sexp comparison to the shared module_to_sexp. |
test/sany/equivalence/dune |
Adds a new OUnit test executable for SANY/TLAPM equivalence. |
test/sany/equivalence/sany_equivalence_tests.ml |
Implements test harness to compare TLAPM vs SANY parse trees (via sexp diff). |
test/sany/equivalence/00_ConstantRefTest.tla |
Adds minimal equivalence corpus module (constants). |
test/sany/equivalence/01_VariableRefTest.tla |
Adds minimal equivalence corpus module (variables). |
test/sany/equivalence/02_OperatorRefTest.tla |
Adds minimal equivalence corpus module (operator refs). |
test/sany/equivalence/03_AssumeRefTest.tla |
Adds minimal equivalence corpus module (ASSUME refs). |
test/sany/equivalence/04_TheoremRefTest.tla |
Adds minimal equivalence corpus module (theorem refs). |
test/sany/equivalence/05_ConstantOperatorTest.tla |
Adds minimal equivalence corpus module (constant operator application). |
test/sany/equivalence/06_OperatorParameterRefTest.tla |
Adds minimal equivalence corpus module (operator parameters). |
test/sany/equivalence/07_ExpressionTest.tla |
Adds minimal equivalence corpus module (basic expression). |
test/sany/equivalence/08_ObviousProofTest.tla |
Adds minimal equivalence corpus module (OBVIOUS proof). |
test/sany/equivalence/09_BasicByProofTest.tla |
Adds minimal equivalence corpus module (BY proof). |
test/sany/equivalence/10_BasicStepProofTest.tla |
Adds minimal equivalence corpus module (step proofs). |
test/sany/equivalence/11_BasicAssumeProveTest.tla |
Adds minimal equivalence corpus module (ASSUME/PROVE). |
test/sany/equivalence/12_BasicCaseStepProofTest.tla |
Adds minimal equivalence corpus module (CASE steps). |
test/sany/equivalence/13_BasicProofStepReferenceTest.tla |
Adds minimal equivalence corpus module (step references). |
test/sany/equivalence/14_BasicPTLProofTest.tla |
Adds minimal equivalence corpus module (PTL proof). |
test/sany/equivalence/15_SufficesProofTest.tla |
Adds minimal equivalence corpus module (SUFFICES). |
test/sany/equivalence/16_NewBoundedAssumeTest.tla |
Adds minimal equivalence corpus module (NEW + bounded). |
test/sany/equivalence/17_ActionOperatorTest.tla |
Adds minimal equivalence corpus module (action operators). |
test/sany/equivalence/18_PrimeOperatorTest.tla |
Adds minimal equivalence corpus module (prime operator). |
test/sany/equivalence/19_HierarchicalProofTest.tla |
Adds minimal equivalence corpus module (hierarchical proofs). |
test/sany/equivalence/20_MixedByProofTest.tla |
Adds minimal equivalence corpus module (mixed BY usage). |
test/sany/equivalence/21_ExtendsBase.tla |
Adds minimal equivalence corpus module (EXTENDS base). |
test/sany/equivalence/21_ExtendsTest.tla |
Adds minimal equivalence corpus module (EXTENDS usage). |
test/sany/equivalence/22_UseDefStepTest.tla |
Adds minimal equivalence corpus module (USE DEF step). |
test/sany/equivalence/23_SufficesAssumeProveTest.tla |
Adds minimal equivalence corpus module (SUFFICES ASSUME/PROVE). |
test/sany/equivalence/24_StepAssumeBoundedNewTest.tla |
Adds minimal equivalence corpus module (ASSUME NEW bounded step). |
test/sany/equivalence/25_ExtendsConstantBase.tla |
Adds minimal equivalence corpus module (EXTENDS + constants base). |
test/sany/equivalence/25_ExtendsConstantStepTest.tla |
Adds minimal equivalence corpus module (EXTENDS + constant proof). |
test/sany/equivalence/26_InstanceWithBase.tla |
Adds minimal equivalence corpus module (INSTANCE base). |
test/sany/equivalence/26_InstanceWithTest.tla |
Adds minimal equivalence corpus module (INSTANCE WITH usage). |
test/sany/equivalence/27_LabelDefinitionTest.tla |
Adds minimal equivalence corpus module (labels in definitions). |
test/sany/equivalence/28_LabelParameterTest.tla |
Adds minimal equivalence corpus module (labels with params). |
test/sany/equivalence/29_LabelInQuantifierTest.tla |
Adds minimal equivalence corpus module (labels in quantifiers). |
test/sany/equivalence/30_HideDefStepTest.tla |
Adds minimal equivalence corpus module (HIDE DEF step). |
test/sany/known_failures/LabeledSubexpression.tla |
Documents a known conversion gap related to labeled subexpressions. |
tlapm.opam |
Adds xmlm opam dependency. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
fc376de to
128d635
Compare
Hidden behind --parser SANY CLI argument These changes add SANY as a possible parser backend, converting SANY's XML Exporter output to TLAPM's internal parse tree format. This is primarily done in src/sany/xml.ml and src/sany/sany/ml. Some modifications were also made to the command line parameters and library code to enable use of SANY. Signed-off-by: Andrew Helwer <ahelwer@pm.me>
128d635 to
190faf5
Compare
|
Hi, I had a look at this pull request. To evaluate how much this could be a drop-in replacement to tlapm's built-in parser, I've patched tlapm (the branch from #275) to default to SANY with an environment variable, and ran the test suite with the variable set. I also had to implement The branch is there: https://github.com/glondu/tlapm/tree/sany In the following, I'll focus on the following workflow:
This includes 281 test cases. The following of this message has been written with the help of Claude. SummaryOf these failures, once error messages were properly surfaced (see "Harness fixes" below), they break down as:
Harness fixes made during this investigationOne place in the code was silently discarding error information, which made root-causing individual failures impossible until fixed:
I quickly patched it using a reference, but a more permanent solution has to be designed — it would be useful for regular test failures too, not just this migration. Main finding: SANY does eager semantic resolution during "parsing"88% of the failures with real error messages (122/138) come from a single structural mismatch, not from 122 independent bugs. TLAPM's own parser and the tree-sitter grammar (which the standardized corpus was written against) are purely syntactic — they build a parse tree without resolving identifiers or checking level constraints. SANY, when invoked via
None of these are TLAPM- or SANY-specific bugs; they're a consequence of running a semantic-analysis-eager parser against a syntax-only test corpus. This needs a decision, not a fix, on one of:
Genuine syntax/grammar divergences (14 cases) — worth individual triageThese are not semantic-resolution noise; they represent real differences in what each parser accepts:
Bonus, unrelated to this migration: found two instances of a genuine upstream SANY bug — Known conversion gaps already caught by custom exceptions
Real AST structural differences (12 cases) — mostly expected, not bugs
Improvements SANY brings "for free" (5 cases) — worth calling out positivelyThese are pre-existing, tracked TLAPM parser bugs where SANY's behavior is more correct than TLAPM's own parser, not a regression:
|
SANY doesn’t provide us with syntax-only today. Instead, it assumes that the resolved UID references have been provided and therefore requires semantic analysis to be done. |
Could you please provide reproducers? |
All the errors can be reproduced using my branch and the following command from the For example, the "internal SANY crash" can be reproduced with the following input: ---- MODULE Test ----
tree_nav == op(a, b)!<<!>>!3!(x, y)!:!@
====SANY output: |
Two consecutive non-name subexpression selectors following an unresolved operator name, as in `op!<<!>>`, make Generator::selectorToNode reach an INTERNAL_ERROR check and abort the parse. That code is reserved for assertions about SANY's own state, so a syntactically valid spec must not reach it; dropping either selector yields the expected Unknown operator error instead. The longer of the two inputs is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Both tests fail today. [Tests][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage renders a recorded message with String.format, but the call sites build their text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore forms the invalid conversion %' and crashes the parser with UnknownFormatConversionException, while one mentioning %% forms a valid specifier and is silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) All four tests fail today. [Tests][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes string values with their TLA+ escapes already resolved, so a spec containing \f cannot be exported at all: XML 1.0 character data cannot hold U+000C, not even as a numeric character reference. The export fails with XML_TRANSFORMATION_FAILURE, which the exporter in turn reports as a bug in itself. The test asserts only that the export succeeds, not how the form feed ends up represented. The remaining escapes do export, and the second test pins that down while recording that their source-level structure is not recoverable from the XML either. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Ref #1313 The form feed test fails today. [Tests][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes string values with their TLA+ escapes already resolved, so a spec whose string literal contains the form feed denoted by the TLA+ escape \f could not be exported at all: XML 1.0 character data cannot hold U+000C, not even as a numeric character reference. The export failed with XML_TRANSFORMATION_FAILURE, which the exporter in turn reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, which no parser accepts. Set the document version to 1.1, which can represent those characters as character references, but only when the built DOM actually contains one. XML 1.1 has seen little use since it appeared and is correspondingly less commonly supported than 1.0, so declaring it for every export would risk breaking consumers over specs that 1.0 represents perfectly well. The export of every spec that XML 1.0 can represent thus stays byte-identical, and only specs that could not be exported at all before are affected. Even so, expat rejects such a character reference no matter which version the document declares. The test asserts only that the export succeeds, not how the form feed ends up represented. The remaining escapes exported before, and the second test pins that down while recording that their source-level structure is not recoverable from the XML either. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Ref #1313 [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Are you referring to SANY or TLAPS parser behavior? |
SANY |
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes the value of a string literal after its TLA+ escapes have been resolved - the parser resolves them, overwriting the source text of the literal, so the value is the only representation of it that survives - and so asks XML character data to hold whatever the spec put in the string. XML 1.0 cannot hold most control characters, not even as numeric character references, so a spec whose literal contains the form feed denoted by \f failed in the serializer with XML_TRANSFORMATION_FAILURE, which the exporter reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, so the export reported success while writing a file that no parser accepts. Reject such a spec instead, with the new XML_UNREPRESENTABLE_CHARACTER, which is not classified as a bug, naming the character and the element holding it. The check walks the built DOM before validation and serialization, so nothing is written when it fires, and follows the XML 1.0 Char production, which also excludes the null character, the surrogates and the noncharacters at the end of the basic multilingual plane; iterating by code point catches a lone surrogate as well. A null character used to be refused by the serializer, and in a comment produced the reference � that no parser reads. XML 1.1 can represent those characters as references and would let the export succeed, but no consumer reads it: xmlm, which TLAPM parses the export with, checks character references against the XML 1.0 Char production whichever version the document declares, and expat rejects them as well. Declaring 1.1 would only move the failure into the consumer while making the version of the output depend on the spec. Not one .tla file in this repository holds a form feed, either. Exporting such a spec at all needs the representation of string values to change, which is #1313 Report a failure the user can act on as an error rather than as the exception that carried it out of the export, keeping the exception text, its stack trace and the request to report a bug for the failures that are bugs. The tests assert that the form feed and a null character are rejected, the latter in both a string literal and a comment. Their modules are generated, since a .tla file holding a null character is hostile to editors and diffs. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage passes its text to String.format, but its call sites built that text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore formed the invalid conversion %' and crashed the parser with UnknownFormatConversionException, while one mentioning %% formed a valid specifier and was silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Convert those call sites in tla2sany to a literal format that takes the name as a parameter; String.format never scans its arguments, so a symbol named % or %% now reaches the message unharmed. This makes the interpolated values available through ErrorDetails::getParameters, as ErrorCode::getParameterCount anticipates, so the codes whose messages carry values now declare how many. The codes whose call sites disagree are declared variadic and say so in a comment; reconciling them is left for whoever standardizes their parameters. [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes the value of a string literal after its TLA+ escapes have been resolved - the parser resolves them, overwriting the source text of the literal, so the value is the only representation of it that survives - and so asks XML character data to hold whatever the spec put in the string. XML 1.0 cannot hold most control characters, not even as numeric character references, so a spec whose literal contains the form feed denoted by \f failed in the serializer with XML_TRANSFORMATION_FAILURE, which the exporter reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, so the export reported success while writing a file that no parser accepts. Reject such a spec instead, with the new XML_UNREPRESENTABLE_CHARACTER, which is not classified as a bug, naming the character and the element holding it. The check walks the built DOM before validation and serialization, so nothing is written when it fires, and follows the XML 1.0 Char production, which also excludes the null character, the surrogates and the noncharacters at the end of the basic multilingual plane; iterating by code point catches a lone surrogate as well. A null character used to be refused by the serializer, and in a comment produced the reference � that no parser reads. XML 1.1 can represent those characters as references and would let the export succeed, but no consumer reads it: xmlm, which TLAPM parses the export with, checks character references against the XML 1.0 Char production whichever version the document declares, and expat rejects them as well. Declaring 1.1 would only move the failure into the consumer while making the version of the output depend on the spec. Not one .tla file in this repository holds a form feed, either. Exporting such a spec at all needs the representation of string values to change, which is #1313 Report a failure the user can act on as an error rather than as the exception that carried it out of the export, keeping the exception text, its stack trace and the request to report a bug for the failures that are bugs. The tests assert that the form feed and a null character are rejected, the latter in both a string literal and a comment. Their modules are generated, since a .tla file holding a null character is hostile to editors and diffs. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage passes its text to String.format, but its call sites built that text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore formed the invalid conversion %' and crashed the parser with UnknownFormatConversionException, while one mentioning %% formed a valid specifier and was silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Convert those call sites in tla2sany to a literal format that takes the name as a parameter; String.format never scans its arguments, so a symbol named % or %% now reaches the message unharmed. This makes the interpolated values available through ErrorDetails::getParameters, as ErrorCode::getParameterCount anticipates, so the codes whose messages carry values now declare how many. The codes whose call sites disagree are declared variadic and say so in a comment; reconciling them is left for whoever standardizes their parameters. [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
|
Page 291 of https://lamport.azurewebsites.net/tla/xmxx00-02-09.pdf defines the legal escapes in strings constants. Page 269 explains that |
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes the value of a string literal after its TLA+ escapes have been resolved - the parser resolves them, overwriting the source text of the literal, so the value is the only representation of it that survives - and so asks XML character data to hold whatever the spec put in the string. XML 1.0 cannot hold most control characters, not even as numeric character references, so a spec whose literal contains the form feed denoted by \f failed in the serializer with XML_TRANSFORMATION_FAILURE, which the exporter reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, so the export reported success while writing a file that no parser accepts. Reject such a spec instead, with the new XML_UNREPRESENTABLE_CHARACTER, which is not classified as a bug, naming the character and the element holding it. The check walks the built DOM before validation and serialization, so nothing is written when it fires, and follows the XML 1.0 Char production, which also excludes the null character, the surrogates and the noncharacters at the end of the basic multilingual plane; iterating by code point catches a lone surrogate as well. A null character used to be refused by the serializer, and in a comment produced the reference � that no parser reads. XML 1.1 can represent those characters as references and would let the export succeed, but no consumer reads it: xmlm, which TLAPM parses the export with, checks character references against the XML 1.0 Char production whichever version the document declares, and expat rejects them as well. Declaring 1.1 would only move the failure into the consumer while making the version of the output depend on the spec. Not one .tla file in this repository holds a form feed, either. Exporting such a spec at all needs the representation of string values to change, which is #1313 Report a failure the user can act on as an error rather than as the exception that carried it out of the export, keeping the exception text, its stack trace and the request to report a bug for the failures that are bugs. The tests assert that the form feed and a null character are rejected, the latter in both a string literal and a comment. Their modules are generated, since a .tla file holding a null character is hostile to editors and diffs. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage passes its text to String.format, but its call sites built that text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore formed the invalid conversion %' and crashed the parser with UnknownFormatConversionException, while one mentioning %% formed a valid specifier and was silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Convert those call sites in tla2sany to a literal format that takes the name as a parameter; String.format never scans its arguments, so a symbol named % or %% now reaches the message unharmed. This makes the interpolated values available through ErrorDetails::getParameters, as ErrorCode::getParameterCount anticipates, so the codes whose messages carry values now declare how many. The codes whose call sites disagree are declared variadic and say so in a comment; reconciling them is left for whoever standardizes their parameters. [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes the value of a string literal after its TLA+ escapes have been resolved - the parser resolves them, overwriting the source text of the literal, so the value is the only representation of it that survives - and so asks XML character data to hold whatever the spec put in the string. XML 1.0 cannot hold most control characters, not even as numeric character references, so a spec whose literal contains the form feed denoted by \f failed in the serializer with XML_TRANSFORMATION_FAILURE, which the exporter reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, so the export reported success while writing a file that no parser accepts. Reject such a spec instead, with the new XML_UNREPRESENTABLE_CHARACTER, which is not classified as a bug, naming the character and the element holding it. The check walks the built DOM before validation and serialization, so nothing is written when it fires, and follows the XML 1.0 Char production, which also excludes the null character, the surrogates and the noncharacters at the end of the basic multilingual plane; iterating by code point catches a lone surrogate as well. A null character used to be refused by the serializer, and in a comment produced the reference � that no parser reads. XML 1.1 can represent those characters as references and would let the export succeed, but no consumer reads it: xmlm, which TLAPM parses the export with, checks character references against the XML 1.0 Char production whichever version the document declares, and expat rejects them as well. Declaring 1.1 would only move the failure into the consumer while making the version of the output depend on the spec. Not one .tla file in this repository holds a form feed, either. Exporting such a spec at all needs the representation of string values to change, which is #1313 Report a failure the user can act on as an error rather than as the exception that carried it out of the export, keeping the exception text, its stack trace and the request to report a bug for the failures that are bugs. The tests assert that the form feed and a null character are rejected, the latter in both a string literal and a comment. Their modules are generated, since a .tla file holding a null character is hostile to editors and diffs. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage passes its text to String.format, but its call sites built that text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore formed the invalid conversion %' and crashed the parser with UnknownFormatConversionException, while one mentioning %% formed a valid specifier and was silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Convert those call sites in tla2sany to a literal format that takes the name as a parameter; String.format never scans its arguments, so a symbol named % or %% now reaches the message unharmed. This makes the interpolated values available through ErrorDetails::getParameters, as ErrorCode::getParameterCount anticipates, so the codes whose messages carry values now declare how many. The codes whose call sites disagree are declared variadic and say so in a comment; reconciling them is left for whoever standardizes their parameters. [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes the value of a string literal after its TLA+ escapes have been resolved - the parser resolves them, overwriting the source text of the literal, so the value is the only representation of it that survives - and so asks XML character data to hold whatever the spec put in the string. XML 1.0 cannot hold most control characters, not even as numeric character references, so a spec whose literal contains the form feed denoted by \f failed in the serializer with XML_TRANSFORMATION_FAILURE, which the exporter reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, so the export reported success while writing a file that no parser accepts. Reject such a spec instead, with the new XML_UNREPRESENTABLE_CHARACTER, which is not classified as a bug, naming the character and the element holding it. The check walks the built DOM before validation and serialization, so nothing is written when it fires, and follows the XML 1.0 Char production, which also excludes the null character, the surrogates and the noncharacters at the end of the basic multilingual plane; iterating by code point catches a lone surrogate as well. A null character used to be refused by the serializer, and in a comment produced the reference � that no parser reads. XML 1.1 can represent those characters as references and would let the export succeed, but no consumer reads it: xmlm, which TLAPM parses the export with, checks character references against the XML 1.0 Char production whichever version the document declares, and expat rejects them as well. Declaring 1.1 would only move the failure into the consumer while making the version of the output depend on the spec. Not one .tla file in this repository holds a form feed, either. Exporting such a spec at all needs the representation of string values to change, which is #1313 Report a failure the user can act on as an error rather than as the exception that carried it out of the export, keeping the exception text, its stack trace and the request to report a bug for the failures that are bugs. The tests assert that the form feed and a null character are rejected, the latter in both a string literal and a comment. Their modules are generated, since a .tla file holding a null character is hostile to editors and diffs. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage passes its text to String.format, but its call sites built that text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore formed the invalid conversion %' and crashed the parser with UnknownFormatConversionException, while one mentioning %% formed a valid specifier and was silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Convert those call sites in tla2sany to a literal format that takes the name as a parameter; String.format never scans its arguments, so a symbol named % or %% now reaches the message unharmed. This makes the interpolated values available through ErrorDetails::getParameters, as ErrorCode::getParameterCount anticipates, so the codes whose messages carry values now declare how many. The codes whose call sites disagree are declared variadic and say so in a comment; reconciling them is left for whoever standardizes their parameters. [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Generator::selectorToNode accumulates the compound name of the operator a subexpression selector applies to, one !-separated part at a time, until the name resolves. It assigned that name on every iteration, so a selector that is not a name, such as !<<, overwrote it with null. The next iteration then found no name and reached the INTERNAL_ERROR check reserved for assertions about SANY's own state, aborting the parse of a syntactically valid spec. Two consecutive non-name selectors following an unresolved operator name, as in `op!<<!>>`, were enough to trigger it, while dropping either selector yielded the expected SYMBOL_UNDEFINED error. Assign the name only for a name selector. The check is now unreachable: curName is null only before the first name has been seen, which is idx = 0 and which the preceding branch already reports as an error the user can act on. `op!<<!>>` now yields the same SYMBOL_UNDEFINED error as `op!<<`. The longer of the two inputs covered by the tests is the Subexpression Tree Navigation case of the syntax corpus, which crashed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The exporter writes the value of a string literal after its TLA+ escapes have been resolved - the parser resolves them, overwriting the source text of the literal, so the value is the only representation of it that survives - and so asks XML character data to hold whatever the spec put in the string. XML 1.0 cannot hold most control characters, not even as numeric character references, so a spec whose literal contains the form feed denoted by \f failed in the serializer with XML_TRANSFORMATION_FAILURE, which the exporter reports as a bug in itself. The same character in a comment fared worse: it was serialized as  into a document declaring XML 1.0, so the export reported success while writing a file that no parser accepts. Reject such a spec instead, with the new XML_UNREPRESENTABLE_CHARACTER, which is not classified as a bug, naming the character and the element holding it. The check walks the built DOM before validation and serialization, so nothing is written when it fires, and follows the XML 1.0 Char production, which also excludes the null character, the surrogates and the noncharacters at the end of the basic multilingual plane; iterating by code point catches a lone surrogate as well. A null character used to be refused by the serializer, and in a comment produced the reference � that no parser reads. XML 1.1 can represent those characters as references and would let the export succeed, but no consumer reads it: xmlm, which TLAPM parses the export with, checks character references against the XML 1.0 Char production whichever version the document declares, and expat rejects them as well. Declaring 1.1 would only move the failure into the consumer while making the version of the output depend on the spec. Not one .tla file in this repository holds a form feed, either. Exporting such a spec at all needs the representation of string values to change, which is #1313 Report a failure the user can act on as an error rather than as the exception that carried it out of the export, keeping the exception text, its stack trace and the request to report a bug for the failures that are bugs. The tests assert that the form feed and a null character are rejected, the latter in both a string literal and a comment. Their modules are generated, since a .tla file holding a null character is hostile to editors and diffs. The \f input is the String with supported escape chars entry of the syntax corpus, which failed while the corpus was run through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Errors::getMessage passes its text to String.format, but its call sites built that text by concatenating a symbol name taken from the spec into a plain string. A spec mentioning the % operator therefore formed the invalid conversion %' and crashed the parser with UnknownFormatConversionException, while one mentioning %% formed a valid specifier and was silently reported as an error about the operator %. The two crashing inputs, `x % y` and `A!B!%(x, y)`, reduce the two failures of this kind found while running the syntax corpus through SANY to evaluate it as TLAPM's parser backend: tlaplus/tlapm#275 (comment) Convert those call sites in tla2sany to a literal format that takes the name as a parameter; String.format never scans its arguments, so a symbol named % or %% now reaches the message unharmed. This makes the interpolated values available through ErrorDetails::getParameters, as ErrorCode::getParameterCount anticipates, so the codes whose messages carry values now declare how many. The codes whose call sites disagree are declared variadic and say so in a comment; reconciling them is left for whoever standardizes their parameters. [Bug][Test][SANY] Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
|
PR tlaplus/tlaplus#1401 includes fixes for the issues you reported. While it has not yet been reviewed and merged, using the changes from the PR should unblock your work in the meantime, if those issues were blocking you. |
|
I've made progress on this. As of 6b8ffe3, there are 44 failures. I've categorized them as follows:
|
… and survey them Two fixes to the upstream section. **tlaplus#286 is ours**, not an external reference, and the plan now says so plainly: same team, opened 2026-07-27, still unanswered, and its four patch families ARE our items 3, 6, 14, 15 and 20 -- already-public proposals re-implemented, not contributions of this branch. What the branch adds on them is what tlaplus#286 could not offer: single-topic reviewable commits with stated invariants and mechanical gates, and attribution per commit instead of per patch set. **Other people's PRs get their own section**, after checking upstream: `master` is at 4600b24, exactly this branch's base, so nothing has landed since the fork and only the open PRs matter. * tlaplus#284 (open, LGTM) kills orphaned provers via `exec setpriv --pdeathsig KILL` when *tlapm dies*. Same family as our item 2, complementary failure mode: ours covers tlapm alive but its kill ignored (SIGHUP set to SIG_IGN by nohup, inherited through exec). Neither subsumes the other, and tlaplus#284 supplies the SIGKILL escalation our fix lacks -- reference it, do not duplicate it. * tlaplus#285 (open) modifies `let_normalize`/`except_normalize`, the two functions item 15 calls per hypothesis. Textual conflict certain; the per-hypothesis equivalence argument must be re-established with the oracle afterwards. Kept in the survey for that reason only. * tlaplus#275 (open) makes SANY an opt-in parser, so item 7 keeps its value -- but the editor floor is now 95 % parse, and SANY does semantic analysis inside "parsing", which item 19 does not assume. * tlaplus#268 (open, extends the merged tlaplus#241) is the feature items 18-19 currently break: the decomposition code actions locate steps by range, and scoped re-elaboration leaves inner positions stale. This is why those modes stay flag-gated. * tlaplus#283 (merged) gives a deterministic Z3 budget -- worth adopting in measurement protocol P2 to remove prover-side variance. * tlaplus#266 (open) changes an SMT axiom, so item 3's subset gate must be re-run against it; tlaplus#248 (open) upgrades Z3 and invalidates absolutes. * tlaplus#264 closed without adopting an LLM policy -- escalated to the TLA+ Foundation board. The stated maintainer position (human first contact, per-commit disclosure of models used) is the one to assume, and the 441-lines-for-most-of-the-gain framing is what answers the review-workload concern behind it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUUoeEmuL3jsYhUb3UrhJH
Hidden behind
--parserSANY CLI argumentThese changes add SANY as a possible parser backend, converting SANY's XML Exporter output to TLAPM's internal parse tree format. This is primarily done in
src/sany/xml.mlandsrc/sany/sany/ml. Some modifications were also made to the command line parameters and library code to enable use of SANY.Supersedes #255; this PR is the same, just with cleaned-up commit history. That PR can be consulted for a timestamped work log.
Ref #213 tlaplus/tlaplus#1313