Skip to content

Commit 17b8754

Browse files
committed
Properly handle all SMT-LIB commands
1 parent f42f4b2 commit 17b8754

File tree

2 files changed

+88
-9
lines changed

2 files changed

+88
-9
lines changed

carcara/src/parser/error.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
ast::{Constant, PrimitivePool, Rc, Sort, Term, TermPool},
5-
parser::Token,
5+
parser::{Command, Token},
66
utils::Range,
77
};
88
use rug::Integer;
@@ -144,6 +144,22 @@ pub enum ParserError {
144144
/// The parser encountered an unknown qualified operator.
145145
#[error("not a valid qualified operator: '{0}'")]
146146
InvalidQualifiedOp(String),
147+
148+
/// The parser encountered a command that is not supported by Carcara.
149+
#[error("unsupported command: '{0}'")]
150+
UnsupportedCommand(Command),
151+
152+
/// There is more than one instance of a command that can only appear once.
153+
#[error("the '{0}' command cannot appear more than once")]
154+
CommandAppearsMoreThanOnce(Command),
155+
156+
/// The parser encountered a proof-only command when parsing the problem.
157+
#[error("found proof-only command '{0}' when parsing problem")]
158+
ProofCommandInProblem(Command),
159+
160+
/// The parser encountered a problem-only command when parsing the proof.
161+
#[error("found problem-only command '{0}' when parsing proof")]
162+
ProblemCommandInProof(Command),
147163
}
148164

149165
/// Returns an error if the length of `sequence` is not in the `expected` range.

carcara/src/parser/mod.rs

+71-8
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,13 @@ impl<'a, R: BufRead> Parser<'a, R> {
670670
pub fn parse_problem(&mut self) -> CarcaraResult<Problem> {
671671
self.problem = Some(Problem::new());
672672

673+
// Some commands can only appear once in an SMT problem
674+
let [mut seen_check_sat, mut seen_exit, mut seen_get_proof, mut seen_set_logic] =
675+
[false; 4];
676+
673677
while self.current_token != Token::Eof {
674678
self.expect_token(Token::OpenParen)?;
679+
let pos = self.current_position;
675680
let command = self.expect_command()?;
676681
match command {
677682
Command::DeclareFun => {
@@ -733,13 +738,17 @@ impl<'a, R: BufRead> Parser<'a, R> {
733738
self.expect_token(Token::CloseParen)?;
734739
self.premises().insert(term);
735740
}
736-
Command::CheckSatAssuming => {
741+
Command::CheckSatAssuming if !seen_check_sat => {
742+
seen_check_sat = true;
743+
737744
self.expect_token(Token::OpenParen)?;
738745
let terms = self.parse_sequence(Self::parse_term, true)?;
739746
self.expect_token(Token::CloseParen)?;
740747
self.premises().extend(terms);
741748
}
742-
Command::SetLogic => {
749+
Command::SetLogic if !seen_set_logic => {
750+
seen_set_logic = true;
751+
743752
let logic = self.expect_symbol()?;
744753
self.expect_token(Token::CloseParen)?;
745754
self.prelude().logic = Some(logic.clone());
@@ -752,12 +761,65 @@ impl<'a, R: BufRead> Parser<'a, R> {
752761
(logic.contains("LRA") || logic.contains("NRA") || logic.contains("RDL"))
753762
&& !logic.contains('I');
754763
}
755-
// TODO: properly handle all commands
756-
_ => {
757-
// If the command is not one of the commands we care about, we just ignore it.
758-
// We do that by reading tokens until the command parenthesis is closed
764+
Command::CheckSat if !seen_check_sat => {
765+
seen_check_sat = true;
766+
self.expect_token(Token::CloseParen)?;
767+
}
768+
Command::Exit if !seen_exit => {
769+
seen_exit = true;
770+
self.expect_token(Token::CloseParen)?;
771+
}
772+
Command::GetProof if !seen_get_proof => {
773+
seen_get_proof = true;
774+
self.expect_token(Token::CloseParen)?;
775+
}
776+
777+
// We only have support for problems that include at most one of these commands. If
778+
// they appear again, we must return an error
779+
Command::CheckSat
780+
| Command::CheckSatAssuming
781+
| Command::Exit
782+
| Command::GetProof
783+
| Command::SetLogic => {
784+
return Err(Error::Parser(
785+
ParserError::CommandAppearsMoreThanOnce(command),
786+
pos,
787+
));
788+
}
789+
790+
// We can safely ignore these commands, since they do not change the problem state
791+
Command::Echo
792+
| Command::GetAssertions
793+
| Command::GetAssignment
794+
| Command::GetInfo
795+
| Command::GetModel
796+
| Command::GetOption
797+
| Command::GetUnsatAssumptions
798+
| Command::GetUnsatCore
799+
| Command::GetValue
800+
| Command::SetInfo
801+
| Command::SetOption => {
759802
self.ignore_until_close_parens()?;
760803
}
804+
805+
// These commands are not supported by Carcara, and we must reject any problem that
806+
// contains them
807+
Command::DeclareDatatype
808+
| Command::DeclareDatatypes
809+
| Command::Pop
810+
| Command::Push
811+
| Command::Reset
812+
| Command::ResetAssertions => {
813+
return Err(Error::Parser(ParserError::UnsupportedCommand(command), pos));
814+
}
815+
816+
// These commands are only allowed when parsing the proof, not the problem
817+
Command::Assume | Command::Step | Command::Anchor => {
818+
return Err(Error::Parser(
819+
ParserError::ProofCommandInProblem(command),
820+
pos,
821+
));
822+
}
761823
}
762824
}
763825
Ok(self.problem.take().unwrap())
@@ -831,10 +893,11 @@ impl<'a, R: BufRead> Parser<'a, R> {
831893
next_subproof_context_id += 1;
832894
continue;
833895
}
834-
// TODO: properly handle all commands
896+
897+
// All other commands can only appear in the problem, not the proof
835898
_ => {
836899
return Err(Error::Parser(
837-
ParserError::UnexpectedToken(Token::Command(command)),
900+
ParserError::ProblemCommandInProof(command),
838901
position,
839902
));
840903
}

0 commit comments

Comments
 (0)