smtlib_syntax/
lexicon.rs

1/* From the spec (p. 22, bottom):
2 *
3 * The character " can itself occur within a string literal only if duplicated.
4 * In other words, after an initial " that starts a literal, a lexer should
5 * treat the sequence "" as an escape sequence denoting a single occurrence of
6 * " within the literal.
7*/
8pub fn escape_string(string: &str) -> String {
9    string.replace(r#"""#, r#""""#)
10}
11
12use std::fmt::Display;
13
14use predicates::{is_printable, is_white_space};
15
16use super::{
17    scripts::CommandType,
18    sexprs::{Sexpr, SpecialConstant},
19    terms::Term,
20};
21
22// TODO: impl Display
23#[derive(Debug, Clone)]
24pub struct InvalidStringError(String);
25
26#[derive(Clone, Debug)]
27pub struct StringConstant(String);
28
29impl StringConstant {
30    pub fn new(string: String) -> Result<Self, InvalidStringError> {
31        if string.chars().all(|c| is_printable(c) || is_white_space(c)) {
32            Ok(Self(string))
33        } else {
34            Err(InvalidStringError(string))
35        }
36    }
37
38    pub fn new_str(string: &str) -> Result<Self, InvalidStringError> {
39        Self::new(string.to_string())
40    }
41
42    pub fn new_expect(string: String, expect_msg: &str) -> Self {
43        Self::new(string).expect(expect_msg)
44    }
45
46    pub fn new_str_expect(string: &str, expect_msg: &str) -> Self {
47        Self::new_expect(string.to_string(), expect_msg)
48    }
49
50    pub fn string_constant(&self) -> &str {
51        &self.0
52    }
53}
54impl From<StringConstant> for SpecialConstant {
55    fn from(value: StringConstant) -> Self {
56        SpecialConstant::String(value)
57    }
58}
59
60impl From<StringConstant> for Sexpr {
61    fn from(value: StringConstant) -> Self {
62        Sexpr::SpecialConstant(value.into())
63    }
64}
65
66impl From<i64> for Sexpr {
67    fn from(value: i64) -> Self {
68        Sexpr::SpecialConstant(value.into())
69    }
70}
71
72impl From<i64> for Term {
73    fn from(value: i64) -> Self {
74        Term::SpecialConstant(value.into())
75    }
76}
77
78impl Display for StringConstant {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, r#""{}""#, escape_string(&self.0))
81    }
82}
83
84pub enum Reserved {
85    Binary,
86    Decimal,
87    Hexadecimal,
88    Numeral,
89    String,
90    Underscore,
91    Bang,
92    As,
93    Let,
94    Exists,
95    Forall,
96    Match,
97    Par,
98    Command(CommandType),
99}
100
101impl Display for Reserved {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        match self {
104            Reserved::Binary => write!(f, "BINARY"),
105            Reserved::Decimal => write!(f, "DECIMAL"),
106            Reserved::Hexadecimal => write!(f, "HEXADECIMAL"),
107            Reserved::Numeral => write!(f, "NUMERAL"),
108            Reserved::String => write!(f, "STRING"),
109            Reserved::Underscore => write!(f, "_"),
110            Reserved::Bang => write!(f, "!"),
111            Reserved::As => write!(f, "as"),
112            Reserved::Let => write!(f, "let"),
113            Reserved::Exists => write!(f, "exists"),
114            Reserved::Forall => write!(f, "forall"),
115            Reserved::Match => write!(f, "match"),
116            Reserved::Par => write!(f, "par"),
117            Reserved::Command(cmd) => match cmd {
118                CommandType::Assert => write!(f, "assert"),
119                CommandType::CheckSat => write!(f, "check-sat"),
120                CommandType::CheckSatAssuming => write!(f, "check-sat-assuming"),
121                CommandType::DeclareConst => write!(f, "declare-const"),
122                CommandType::DeclareDatatype => write!(f, "declare-datatype"),
123                CommandType::DeclareDatatypes => write!(f, "declare-datatypes"),
124                CommandType::DeclareFun => write!(f, "declare-fun"),
125                CommandType::DeclareSort => write!(f, "declare-sort"),
126                CommandType::DefineFun => write!(f, "define-fun"),
127                CommandType::DefineFunRec => write!(f, "define-fun-rec"),
128                CommandType::DefineFunsRec => write!(f, "define-funs-rec"),
129                CommandType::DefineSort => write!(f, "define_sort"),
130                CommandType::Echo => write!(f, "echo"),
131                CommandType::Exit => write!(f, "exit"),
132                CommandType::GetAssertions => write!(f, "get-assertions"),
133                CommandType::GetAssignment => write!(f, "get-assignment"),
134                CommandType::GetModel => write!(f, "get-model"),
135                CommandType::GetOption => write!(f, "get-option"),
136                CommandType::GetProof => write!(f, "get-proof"),
137                CommandType::GetUnsatAssumptions => write!(f, "get-unsat-assumptions"),
138                CommandType::GetUnsatCore => write!(f, "get-unsat-core"),
139                CommandType::GetValue => write!(f, "get-value"),
140                CommandType::Pop => write!(f, "pop"),
141                CommandType::Push => write!(f, "push"),
142                CommandType::Reset => write!(f, "reset"),
143                CommandType::ResetAssertions => write!(f, "reset-assertions"),
144                CommandType::SetLogic => write!(f, "set-logic"),
145            },
146        }
147    }
148}
149
150impl From<Reserved> for Sexpr {
151    fn from(value: Reserved) -> Self {
152        Sexpr::Reserved(value)
153    }
154}
155
156#[derive(Clone, Debug, PartialEq, Eq)]
157pub struct Symbol {
158    symbol: String,
159    is_quoted: bool,
160}
161
162impl From<Symbol> for Sexpr {
163    fn from(value: Symbol) -> Self {
164        Sexpr::Symbol(value)
165    }
166}
167
168impl Display for Symbol {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        if self.is_quoted() {
171            write!(f, "|")?;
172            write!(f, "{}", self.symbol)?;
173            write!(f, "|")
174        } else {
175            write!(f, "{}", self.symbol)
176        }
177    }
178}
179
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct InvalidSymbolError(String);
182
183impl Symbol {
184    pub fn new(symbol: String) -> Result<Symbol, InvalidSymbolError> {
185        if symbol.contains('|') || symbol.contains('\\') {
186            Err(InvalidSymbolError(symbol))
187        } else {
188            let is_quoted = !predicates::is_simple_symbol(&symbol);
189
190            Ok(Symbol { symbol, is_quoted })
191        }
192    }
193
194    pub fn symbol(&self) -> &str {
195        &self.symbol
196    }
197    pub fn is_quoted(&self) -> bool {
198        self.is_quoted
199    }
200}
201
202#[derive(Clone, Debug)]
203pub struct Keyword(String);
204
205impl Keyword {
206    pub fn keyword(&self) -> &str {
207        &self.0
208    }
209}
210
211impl From<Keyword> for Sexpr {
212    fn from(value: Keyword) -> Self {
213        Sexpr::Keyword(value)
214    }
215}
216
217impl std::fmt::Display for Keyword {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        write!(f, ":{}", self.0)
220    }
221}
222
223pub mod predicates {
224    const RESERVED: [&str; 13] = [
225        "!",
226        "BINARY",
227        "DECIMAL",
228        "HEXADECIMAL",
229        "NUMERAL",
230        "STRING",
231        "_",
232        "as",
233        "exists",
234        "forall",
235        "let",
236        "match",
237        "par",
238    ];
239
240    pub fn is_white_space(c: char) -> bool {
241        match c {
242            '\t' | '\r' | '\n' | ' ' => true,
243            _ => false,
244        }
245    }
246
247    pub fn is_printable(c: char) -> bool {
248        match c {
249            '\0'..='\x1f' | '\x7f' => false,
250            _ => true,
251        }
252    }
253
254    pub fn is_digit(c: char) -> bool {
255        match c {
256            '0'..='9' => true,
257            _ => false,
258        }
259    }
260
261    pub fn is_letter(c: char) -> bool {
262        match c {
263            'A'..='Z' | 'a'..='z' => true,
264            _ => false,
265        }
266    }
267
268    pub fn is_numeral(s: &str) -> bool {
269        if s == "0" {
270            return true;
271        }
272
273        for (i, c) in s.chars().enumerate() {
274            match (i, c) {
275                // only digits allowed
276                (_, c) if !is_digit(c) => return false,
277
278                // no leading zeroes allowed
279                (0, '0') => return false,
280
281                // accept the rest
282                _ => {}
283            }
284        }
285
286        true
287    }
288
289    pub fn is_simple_symbol(s: &str) -> bool {
290        /* A simple symbol is any non-empty sequence of elements of
291         * 〈letter 〉 and 〈digit〉 and the characters
292         *      ~ ! @ $ % ^ & * _ - + = < > . ? /
293         * that does not start with a digit and is not a reserved word.
294         * */
295
296        let legal_nonletters = "~!@$%^&*_-+=<>.?/";
297
298        for (i, c) in s.chars().enumerate() {
299            match (i, c) {
300                // no leading digits allowed
301                (0, c) if is_digit(c) => return false,
302
303                // a non-empty sequence of letters, digits, and the characters (...)
304                (_, c) if is_letter(c) || is_digit(c) || legal_nonletters.contains(c) => {}
305
306                // anything else is forbidden
307                _ => return false,
308            }
309        }
310
311        !s.is_empty() && !RESERVED.contains(&s)
312    }
313}