Skip to content

Conversation

@aqjune-aws
Copy link
Contributor

@aqjune-aws aqjune-aws commented Oct 31, 2025

This patch adds

  1. A translator from SMT.Term -> SMTDDM.Term as well as
  2. A string converter from SMTDDM.Term -> String
  3. Hooks the converter to SMT/DL/Encoder.lean, only when it is encoding a primitive term (as a small-step start!)
  4. Adds the get-option SMT command and relevant syntax categories (mentioned in the previous pull request).
  5. Slightly changes the .real constructor of the old SMT Term, from String to Decimal.

After 3, I checked the generated .smt2 files at vcs/ dir, and confirmed that they are equivalent modulo spaces! :)

There are a few features in the string converters that are unimplemented; they will be added later when Encoder.lean uses the DDM string converter more.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

This patch adds a translator from SMT.Term to SMTDDM.Term, and also has a
small code snippet that prints the SMTDDM.Term into the format.

There are missing features, which will be addressed in the following commits.

- Whitespaces must exist between the printed subterms(!)
- Option types, triggers, real-number/bit-vector constants are left as unimplemented.
@aqjune-aws aqjune-aws marked this pull request as ready for review November 6, 2025 20:42
// This collides with the ':named' term attribute. This file has the example.
// op info_flag_name () : InfoFlag => ":name";
op info_flag_reasonu () : InfoFlag => ":reason-unknown";
op info_flag_version () : InfoFlag => ":version";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and below are updates for the requested option related SMT commands.

…cimal_neg and sc_numeral_neg to separately treat negative numerals

Note that negative integers like '-1231' are symbols in Std! (Sec 3.1. Lexicon)
The only way to create a unary symbol is through idenitifers, but this
makes its DDM format wrapped with pipes, like '|-1231|`. Since such
representation cannot be recognized by Z3, make a workaround which is to have
separate `*_neg` categories for sc_numeral.
| bool : Bool → TermPrim
| int : Int → TermPrim
| real : String → TermPrim
| real : Decimal → TermPrim
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finally changes the argument of SMT .real term from String to Decimal!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hurray!

Copy link
Contributor

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to be able to parse ":pattern" as well for quantifiers, I think that will be necessary to convert the B3 dialect.

// sign is not a part of the standard, but it seems CVC5 and Z3
// support this for convenience.
// Note that negative integers like '-1231' are symbols in Std! (Sec 3.1. Lexicon)
// The only way to create a unary symbol is through idenitifers, but this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The only way to create a unary symbol is through idenitifers, but this
// The only way to create a unary symbol is through identifiers, but this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, thanks!

// The only way to create a unary symbol is through idenitifers, but this
// makes its DDM format wrapped with pipes, like '|-1231|`. Since such
// representation cannot be recognized by Z3, make a workaround which is to have
// separate `*_neg` categories for sc_numeral.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, same as for the + operator. Thanks for the clarification.

op se_keyword (s:Keyword) : SExpr => s;

op se_ls (s:Seq SExpr) : SExpr => "(" s ")";
category SeqPSExpr; // For spacing, P for "+"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, excellent workaround until we have a SpaceSepBy type one day (which I would approve)



// 3. Identifier
// 3. Identifier. Use 'SMTIdentifier' because the 'Identifier' category is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this possible to move the declaration of category SMTIdentifier up so that it's just below this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this in the new commit.

op iden_simple (s:Symbol) : Identifier => s;
op iden_indexed (s:Symbol, i0:Index, il:Seq Index) : Identifier =>
"(" "_" s i0 il ")";
op iden_indexed (s:Symbol, si:SeqPIndex) : SMTIdentifier =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between SeqPSExpr and SeqPIndex? Since it's the concrete syntax tree, can we use only one of both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the existing syntax rule of Identifier is using <index>+ like this (page 102):

⟨identifier ⟩ ::= ⟨symbol ⟩ | ( _ ⟨symbol ⟩ ⟨index ⟩+ )

Probably SeqPSExpr will be a bit more general than SeqPIndex.

| .bool b₁, .bool b₂ => b₁ < b₂
| .int i₁, .int i₂ => i₁ < i₂
| .real r₁, .real r₂ => r₁ < r₂ -- TODO
| .real r₁, .real r₂ => r₁.toRat < r₂.toRat -- TODO
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the TODO still applicable? If so, can you tell what the TODO is about, if not can you remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

op exists_smt (svs: SeqPSortedVar, t:Term) : Term =>
"(" "exists" "(" svs ")" t ")";
op bang (t:Term, attrs:SeqPAttribute) : Term =>
"(" "!" t " " attrs ")";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting how we can give parsing errors directly on these constructs rather than decoding general S-Expressions. I like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also follows the definition of <term> category in the doc (link) . :) Page 103 has this.

@aqjune-aws
Copy link
Contributor Author

I would like to be able to parse ":pattern" as well for quantifiers, I think that will be necessary to convert the B3 dialect.

I added an example that has :pattern. It seems there is no explicit Pattern syntax category in DDM, but it can still be still parsed as a part of Term.

op declare_datatype (s:Symbol, so:SMTSort) : Command =>
"(" "declare-datatype" s so ")";
"(" "declare-datatype " s so ")";
// TODO: declare-datatypes; what is ^(n+1)?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR, but I think it just means that there are k $\geq$ 1 datatype declarations following the declare-datatypes syntax.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense..! I will fix this in the following commit.

return .smtsort_param srnone (mkIdentifier id) argtys

-- List of SortedVar to SeqPSortedVar.
-- Hope this could be elided away later. :(
Copy link
Contributor

@joscoh joscoh Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by this being elided away? Maybe separately, this and the next function are basically the same thing, so they could be combined into something like the following:

def list_to_ne_gen {nelist : Type → Type} (single : α → nelist α) (cons: α → nelist α → nelist α)  (l: List α) (Hl: ¬l.isEmpty = true) : nelist α :=
  match h: l with
  | [] => by grind
  | [x] => single x
  | x :: y :: t => cons x (list_to_ne_gen single cons (y :: t) (by grind)) 
  
def list_to_optlist {nelist : Type → Type} (single : α → nelist α) (cons: α → nelist α → nelist α) (l: List α) : Option (nelist α) :=
  if h: l.isEmpty then .none else .some (list_to_ne_gen single cons l h)

And then this can be instantiated for both functions. Not sure which is better, don't feel strongly one way or the other.

Copy link
Contributor Author

@aqjune-aws aqjune-aws Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant I wanted to have remove these separate definitions of SeqP* to describe nonempty lists in DDM. :)
That being said, I think the 'SeqP' prefix doesn't seem straightforward to understand; I will fix SeqPSortedVar to SortedVarList.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me think that it might be worth it to have a more convenient way to work with nonempty lists in the DDM. This shows up all over the place, including for datatype constructors.

Copy link
Contributor

@shigoel shigoel Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to tackle this in a subsequent PR, but may I request you to add some file-level comments that describe what's going on in general in the file?
https://github.com/strata-org/Strata/blob/main/CONTRIBUTING.md#file-organization

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I added one. :) I must have added this in advance to avoid making approvals stale :)

shigoel
shigoel previously approved these changes Jan 6, 2026
joscoh
joscoh previously approved these changes Jan 6, 2026
@aqjune-aws aqjune-aws dismissed stale reviews from joscoh and shigoel via 9e5751d January 6, 2026 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants