Skip to content

Commit 0a403a3

Browse files
authored
Merge pull request #11 from coord-e/existential-in-annot
Parse existentials in annotations
2 parents ad2262c + 9a6a2b7 commit 0a403a3

8 files changed

Lines changed: 234 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,33 @@ jobs:
2424
runs-on: ubuntu-latest
2525
permissions:
2626
contents: read
27+
env:
28+
COAR_IMAGE: ghcr.io/hiroshi-unno/coar@sha256:73144ed27a02b163d1a71b41b58f3b5414f12e91326015600cfdca64ff19f011
2729
steps:
2830
- uses: actions/checkout@v4
2931
- uses: ./.github/actions/setup-z3
32+
- name: Setup thrust-pcsat-wrapper
33+
run: |
34+
docker pull "$COAR_IMAGE"
35+
36+
cat <<"EOF" > thrust-pcsat-wrapper
37+
#!/bin/bash
38+
39+
smt2=$(mktemp -p . --suffix .smt2)
40+
trap "rm -f $smt2" EXIT
41+
cp "$1" "$smt2"
42+
out=$(
43+
docker run --rm -v "$PWD:/mnt" -w /root/coar "$COAR_IMAGE" \
44+
main.exe -c ./config/solver/pcsat_tbq_ar.json -p pcsp "/mnt/$smt2"
45+
)
46+
exit_code=$?
47+
echo "${out%,*}"
48+
exit "$exit_code"
49+
EOF
50+
chmod +x thrust-pcsat-wrapper
51+
52+
mkdir -p ~/.local/bin
53+
mv thrust-pcsat-wrapper ~/.local/bin/thrust-pcsat-wrapper
3054
- run: rustup show
3155
- uses: Swatinem/rust-cache@v2
3256
- run: cargo test

src/annot.rs

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_ast::token::{BinOpToken, Delimiter, LitKind, Token, TokenKind};
1212
use rustc_ast::tokenstream::{RefTokenTreeCursor, Spacing, TokenStream, TokenTree};
1313
use rustc_index::IndexVec;
1414
use rustc_span::symbol::Ident;
15+
use std::collections::HashMap;
1516

1617
use crate::chc;
1718
use crate::pretty::PrettyDisplayExt as _;
@@ -185,6 +186,7 @@ impl<T> FormulaOrTerm<T> {
185186
struct Parser<'a, T> {
186187
resolver: T,
187188
cursor: RefTokenTreeCursor<'a>,
189+
formula_existentials: HashMap<String, chc::Sort>,
188190
}
189191

190192
impl<'a, T> Parser<'a, T>
@@ -232,6 +234,15 @@ where
232234
}
233235
}
234236

237+
fn expect_next_ident(&mut self) -> Result<Ident> {
238+
let t = self.next_token("ident")?;
239+
if let Some((ident, _)) = t.ident() {
240+
Ok(ident)
241+
} else {
242+
Err(ParseAttrError::unexpected_token("ident", t.clone()))
243+
}
244+
}
245+
235246
fn consume(&mut self) {
236247
self.cursor.next().unwrap();
237248
}
@@ -296,6 +307,7 @@ where
296307
let mut parser = Parser {
297308
resolver: self.boxed_resolver(),
298309
cursor: s.trees(),
310+
formula_existentials: self.formula_existentials.clone(),
299311
};
300312
let formula_or_term = parser.parse_formula_or_term_or_tuple()?;
301313
parser.end_of_input()?;
@@ -305,9 +317,17 @@ where
305317
};
306318

307319
let formula_or_term = if let Some((ident, _)) = t.ident() {
308-
match ident.as_str() {
309-
"true" => FormulaOrTerm::Formula(chc::Formula::top()),
310-
"false" => FormulaOrTerm::Formula(chc::Formula::bottom()),
320+
match (
321+
ident.as_str(),
322+
self.formula_existentials.get(ident.name.as_str()),
323+
) {
324+
("true", _) => FormulaOrTerm::Formula(chc::Formula::top()),
325+
("false", _) => FormulaOrTerm::Formula(chc::Formula::bottom()),
326+
(_, Some(sort)) => {
327+
let var =
328+
chc::Term::FormulaExistentialVar(sort.clone(), ident.name.to_string());
329+
FormulaOrTerm::Term(var, sort.clone())
330+
}
311331
_ => {
312332
let (v, sort) = self.resolve(ident)?;
313333
FormulaOrTerm::Term(chc::Term::var(v), sort)
@@ -575,8 +595,94 @@ where
575595
Ok(formula_or_term)
576596
}
577597

598+
fn parse_exists(&mut self) -> Result<FormulaOrTerm<T::Output>> {
599+
match self.look_ahead_token(0) {
600+
Some(Token {
601+
kind: TokenKind::Ident(sym, _),
602+
..
603+
}) if sym.as_str() == "exists" => {
604+
self.consume();
605+
let mut vars = Vec::new();
606+
loop {
607+
let ident = self.expect_next_ident()?;
608+
self.expect_next_token(TokenKind::Colon, ":")?;
609+
let sort = self.parse_sort()?;
610+
vars.push((ident.name.to_string(), sort));
611+
match self.next_token(". or ,")? {
612+
Token {
613+
kind: TokenKind::Comma,
614+
..
615+
} => {}
616+
Token {
617+
kind: TokenKind::Dot,
618+
..
619+
} => break,
620+
t => return Err(ParseAttrError::unexpected_token(". or ,", t.clone())),
621+
}
622+
}
623+
self.formula_existentials.extend(vars.iter().cloned());
624+
let formula = self
625+
.parse_formula_or_term()?
626+
.into_formula()
627+
.ok_or_else(|| ParseAttrError::unexpected_term("in exists formula"))?;
628+
for (name, _) in &vars {
629+
self.formula_existentials.remove(name);
630+
}
631+
Ok(FormulaOrTerm::Formula(chc::Formula::exists(vars, formula)))
632+
}
633+
_ => self.parse_binop_5(),
634+
}
635+
}
636+
578637
fn parse_formula_or_term(&mut self) -> Result<FormulaOrTerm<T::Output>> {
579-
self.parse_binop_5()
638+
self.parse_exists()
639+
}
640+
641+
fn parse_sort(&mut self) -> Result<chc::Sort> {
642+
let tt = self.next_token_tree("sort")?.clone();
643+
match tt {
644+
TokenTree::Token(
645+
Token {
646+
kind: TokenKind::Ident(sym, _),
647+
..
648+
},
649+
_,
650+
) => {
651+
let sort = match sym.as_str() {
652+
"bool" => chc::Sort::bool(),
653+
"int" => chc::Sort::int(),
654+
"string" => unimplemented!(),
655+
"null" => chc::Sort::null(),
656+
"fn" => unimplemented!(),
657+
_ => unimplemented!(),
658+
};
659+
Ok(sort)
660+
}
661+
TokenTree::Delimited(_, _, Delimiter::Parenthesis, ts) => {
662+
let mut parser = Parser {
663+
resolver: self.boxed_resolver(),
664+
cursor: ts.trees(),
665+
formula_existentials: self.formula_existentials.clone(),
666+
};
667+
let mut sorts = Vec::new();
668+
loop {
669+
sorts.push(parser.parse_sort()?);
670+
match parser.look_ahead_token(0) {
671+
Some(Token {
672+
kind: TokenKind::Comma,
673+
..
674+
}) => {
675+
parser.consume();
676+
}
677+
None => break,
678+
Some(t) => return Err(ParseAttrError::unexpected_token(",", t.clone())),
679+
}
680+
}
681+
parser.end_of_input()?;
682+
Ok(chc::Sort::tuple(sorts))
683+
}
684+
t => Err(ParseAttrError::unexpected_token_tree("sort", t.clone())),
685+
}
580686
}
581687

582688
fn parse_ty(&mut self) -> Result<rty::Type<T::Output>> {
@@ -662,6 +768,7 @@ where
662768
let mut parser = Parser {
663769
resolver: self.boxed_resolver(),
664770
cursor: ts.trees(),
771+
formula_existentials: self.formula_existentials.clone(),
665772
};
666773
let mut rtys = Vec::new();
667774
loop {
@@ -697,6 +804,7 @@ where
697804
let mut parser = Parser {
698805
resolver: self.boxed_resolver(),
699806
cursor: ts.trees(),
807+
formula_existentials: self.formula_existentials.clone(),
700808
};
701809
let self_ident = if matches!(
702810
parser.look_ahead_token(1),
@@ -720,6 +828,7 @@ where
720828
let mut parser = Parser {
721829
resolver: RefinementResolver::new(self.boxed_resolver()),
722830
cursor: parser.cursor,
831+
formula_existentials: self.formula_existentials.clone(),
723832
};
724833
if let Some(self_ident) = self_ident {
725834
parser.resolver.set_self(self_ident, ty.to_sort());
@@ -859,6 +968,7 @@ where
859968
let mut parser = Parser {
860969
resolver: &self.resolver,
861970
cursor: ts.trees(),
971+
formula_existentials: Default::default(),
862972
};
863973
let rty = parser.parse_rty()?;
864974
parser.end_of_input()?;
@@ -869,6 +979,7 @@ where
869979
let mut parser = Parser {
870980
resolver: &self.resolver,
871981
cursor: ts.trees(),
982+
formula_existentials: Default::default(),
872983
};
873984
let formula = parser.parse_annot_formula()?;
874985
parser.end_of_input()?;

src/chc.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ pub enum Term<V = TermVarIdx> {
406406
TupleProj(Box<Term<V>>, usize),
407407
DatatypeCtor(DatatypeSort, DatatypeSymbol, Vec<Term<V>>),
408408
DatatypeDiscr(DatatypeSymbol, Box<Term<V>>),
409+
/// Used in [`Formula`] to represent existentially quantified variables appearing in annotations.
410+
FormulaExistentialVar(Sort, String),
409411
}
410412

411413
impl<'a, 'b, D, V> Pretty<'a, D, termcolor::ColorSpec> for &'b Term<V>
@@ -475,6 +477,7 @@ where
475477
Term::DatatypeDiscr(_, t) => allocator
476478
.text("discriminant")
477479
.append(t.pretty(allocator).parens()),
480+
Term::FormulaExistentialVar(_, name) => allocator.text(name.clone()),
478481
}
479482
}
480483
}
@@ -521,6 +524,7 @@ impl<V> Term<V> {
521524
args.into_iter().map(|t| t.subst_var(&mut f)).collect(),
522525
),
523526
Term::DatatypeDiscr(d_sym, t) => Term::DatatypeDiscr(d_sym, Box::new(t.subst_var(f))),
527+
Term::FormulaExistentialVar(sort, name) => Term::FormulaExistentialVar(sort, name),
524528
}
525529
}
526530

@@ -562,15 +566,18 @@ impl<V> Term<V> {
562566
Term::TupleProj(t, i) => t.sort(var_sort).tuple_elem(*i),
563567
Term::DatatypeCtor(sort, _, _) => sort.clone().into(),
564568
Term::DatatypeDiscr(_, _) => Sort::int(),
569+
Term::FormulaExistentialVar(sort, _) => sort.clone(),
565570
}
566571
}
567572

568573
fn fv_impl(&self) -> Box<dyn Iterator<Item = &V> + '_> {
569574
match self {
570575
Term::Var(v) => Box::new(std::iter::once(v)),
571-
Term::Null | Term::Bool(_) | Term::Int(_) | Term::String(_) => {
572-
Box::new(std::iter::empty())
573-
}
576+
Term::Null
577+
| Term::Bool(_)
578+
| Term::Int(_)
579+
| Term::String(_)
580+
| Term::FormulaExistentialVar { .. } => Box::new(std::iter::empty()),
574581
Term::Box(t) => t.fv_impl(),
575582
Term::Mut(t1, t2) => Box::new(t1.fv_impl().chain(t2.fv_impl())),
576583
Term::BoxCurrent(t) => t.fv_impl(),
@@ -1083,6 +1090,7 @@ pub enum Formula<V = TermVarIdx> {
10831090
Not(Box<Formula<V>>),
10841091
And(Vec<Formula<V>>),
10851092
Or(Vec<Formula<V>>),
1093+
Exists(Vec<(String, Sort)>, Box<Formula<V>>),
10861094
}
10871095

10881096
impl<V> Default for Formula<V> {
@@ -1124,6 +1132,25 @@ where
11241132
);
11251133
inner.group()
11261134
}
1135+
Formula::Exists(vars, fo) => {
1136+
let vars = allocator.intersperse(
1137+
vars.iter().map(|(name, sort)| {
1138+
allocator
1139+
.text(name.clone())
1140+
.append(allocator.text(":"))
1141+
.append(allocator.text(" "))
1142+
.append(sort.pretty(allocator))
1143+
}),
1144+
allocator.text(", ").append(allocator.line()),
1145+
);
1146+
allocator
1147+
.text("∃")
1148+
.append(vars)
1149+
.append(allocator.text("."))
1150+
.append(allocator.line())
1151+
.append(fo.pretty(allocator).nest(2))
1152+
.group()
1153+
}
11271154
}
11281155
}
11291156
}
@@ -1139,7 +1166,9 @@ impl<V> Formula<V> {
11391166
D::Doc: Clone,
11401167
{
11411168
match self {
1142-
Formula::And(_) | Formula::Or(_) => self.pretty(allocator).parens(),
1169+
Formula::And(_) | Formula::Or(_) | Formula::Exists { .. } => {
1170+
self.pretty(allocator).parens()
1171+
}
11431172
_ => self.pretty(allocator),
11441173
}
11451174
}
@@ -1158,6 +1187,7 @@ impl<V> Formula<V> {
11581187
Formula::Not(fo) => fo.is_bottom(),
11591188
Formula::And(fs) => fs.iter().all(Formula::is_top),
11601189
Formula::Or(fs) => fs.iter().any(Formula::is_top),
1190+
Formula::Exists(_, fo) => fo.is_top(),
11611191
}
11621192
}
11631193

@@ -1167,6 +1197,7 @@ impl<V> Formula<V> {
11671197
Formula::Not(fo) => fo.is_top(),
11681198
Formula::And(fs) => fs.iter().any(Formula::is_bottom),
11691199
Formula::Or(fs) => fs.iter().all(Formula::is_bottom),
1200+
Formula::Exists(_, fo) => fo.is_bottom(),
11701201
}
11711202
}
11721203

@@ -1197,6 +1228,10 @@ impl<V> Formula<V> {
11971228
}
11981229
}
11991230

1231+
pub fn exists(vars: Vec<(String, Sort)>, body: Self) -> Self {
1232+
Formula::Exists(vars, Box::new(body))
1233+
}
1234+
12001235
pub fn subst_var<F, W>(self, f: F) -> Formula<W>
12011236
where
12021237
F: FnMut(V) -> Term<W>,
@@ -1210,6 +1245,7 @@ impl<V> Formula<V> {
12101245
Formula::And(fs.into_iter().map(|fo| fo.subst_var(&mut f)).collect())
12111246
}
12121247
Formula::Or(fs) => Formula::Or(fs.into_iter().map(|fo| fo.subst_var(&mut f)).collect()),
1248+
Formula::Exists(vars, fo) => Formula::Exists(vars, Box::new(fo.subst_var(f))),
12131249
}
12141250
}
12151251

@@ -1224,6 +1260,7 @@ impl<V> Formula<V> {
12241260
Formula::Not(fo) => Formula::Not(Box::new(fo.map_var(&mut f))),
12251261
Formula::And(fs) => Formula::And(fs.into_iter().map(|fo| fo.map_var(&mut f)).collect()),
12261262
Formula::Or(fs) => Formula::Or(fs.into_iter().map(|fo| fo.map_var(&mut f)).collect()),
1263+
Formula::Exists(vars, fo) => Formula::Exists(vars, Box::new(fo.map_var(f))),
12271264
}
12281265
}
12291266

@@ -1237,6 +1274,7 @@ impl<V> Formula<V> {
12371274
Formula::Not(fo) => Box::new(fo.fv()),
12381275
Formula::And(fs) => Box::new(fs.iter().flat_map(Formula::fv)),
12391276
Formula::Or(fs) => Box::new(fs.iter().flat_map(Formula::fv)),
1277+
Formula::Exists(_, fo) => Box::new(fo.fv()),
12401278
}
12411279
}
12421280

@@ -1250,6 +1288,7 @@ impl<V> Formula<V> {
12501288
Formula::Not(fo) => Box::new(fo.iter_atoms()),
12511289
Formula::And(fs) => Box::new(fs.iter().flat_map(Formula::iter_atoms)),
12521290
Formula::Or(fs) => Box::new(fs.iter().flat_map(Formula::iter_atoms)),
1291+
Formula::Exists(_, fo) => Box::new(fo.iter_atoms()),
12531292
}
12541293
}
12551294

@@ -1291,6 +1330,9 @@ impl<V> Formula<V> {
12911330
*self = fs.pop().unwrap();
12921331
}
12931332
}
1333+
Formula::Exists(_, fo) => {
1334+
fo.simplify();
1335+
}
12941336
}
12951337
}
12961338
}

src/chc/format_context.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ fn term_sorts(clause: &chc::Clause, t: &chc::Term, sorts: &mut BTreeSet<chc::Sor
5757
}
5858
}
5959
chc::Term::DatatypeDiscr(_, t) => term_sorts(clause, t, sorts),
60+
chc::Term::FormulaExistentialVar(_, _) => {}
6061
}
6162
}
6263

0 commit comments

Comments
 (0)