| 1 | | % (c) 2014-2026 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, |
| 2 | | % Heinrich Heine Universitaet Duesseldorf |
| 3 | | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) |
| 4 | | |
| 5 | | :- module(smtlib2_parser,[parse_smtlib2/2, get_smtlib_lines_read/1]). |
| 6 | | |
| 7 | | :- use_module(probsrc(module_information),[module_info/2]). |
| 8 | | :- module_info(group,smtlib). |
| 9 | | :- module_info(description,'The SMT-LIB 2 Interpreter - Parser'). |
| 10 | | |
| 11 | | :- use_module(library(lists),[append/2]). |
| 12 | | :- use_module(probsrc(debug),[debug_println/2]). |
| 13 | | :- use_module(probsrc(error_manager),[add_error/3, add_warning/3, add_message/3]). |
| 14 | | |
| 15 | | :- set_prolog_flag(double_quotes, codes). |
| 16 | | |
| 17 | | parse_smtlib2(Source, ListOfCommands) :- |
| 18 | | debug_println(5,parsing_smtlib2(Source)), |
| 19 | | reset_parser, |
| 20 | | (phrase(commands(ListOfCommands),Source) |
| 21 | | -> debug_println(5,finished_parsing) |
| 22 | | ; line_counter(Line), |
| 23 | | add_error(smtlib2_parser,'Parsing failed at line:',Line), |
| 24 | | fail |
| 25 | | ). |
| 26 | | |
| 27 | | % meta magic |
| 28 | | % at_least_once consumes rule A at least once and returns a list |
| 29 | | % multiple_times consumes rule A zero or more times |
| 30 | | |
| 31 | | :- meta_predicate multiple_times(3,-,-,-). |
| 32 | | :- meta_predicate at_least_once(3,-,-,-). |
| 33 | | at_least_once(A,[Partial|List]) --> |
| 34 | ? | call(A,Partial), ws, |
| 35 | | !, |
| 36 | ? | multiple_times(A,List). |
| 37 | | at_least_once(A,_) --> err_message(A). |
| 38 | | |
| 39 | | err_message(Expected) --> err_message('Unrecognized',Expected). |
| 40 | | err_message(Kind,Expected) --> |
| 41 | | {line_counter(N), format_with_colour_nl(user_error,[red],"! ~w ~w on line ~w.",[Kind,Expected,N]),fail}. |
| 42 | | err_message(_,_) --> any_chars_but_newline(S), !, {format_with_colour_nl(user_error,[red],"! '~s'",[S]),fail}. |
| 43 | | |
| 44 | | multiple_times(A,[Partial|List]) --> |
| 45 | ? | call(A,Partial), ws, |
| 46 | ? | multiple_times(A,List). |
| 47 | | multiple_times(_A,[],In,In). |
| 48 | | |
| 49 | | |
| 50 | | :- use_module(probsrc(tools_printing),[format_with_colour_nl/4]). |
| 51 | | |
| 52 | | % grammar rules |
| 53 | ? | commands(Cmds) --> real_ws, !, commands(Cmds). |
| 54 | | commands(Commands) --> comment(_), |
| 55 | | {debug_println(19,comment_line)},!, |
| 56 | | commands(Commands). |
| 57 | | commands([Command|Commands]) --> |
| 58 | ? | command(Command), {debug_println(19,command(Command))}, !, |
| 59 | | commands(Commands). |
| 60 | | commands([]) --> !. |
| 61 | | commands(_) --> err_message(command). |
| 62 | | |
| 63 | | reset_parser :- retractall(line_counter(_)), assertz(line_counter(1)). |
| 64 | | :- dynamic line_counter/1. |
| 65 | | line_counter(1). |
| 66 | | get_smtlib_lines_read(Nr) :- (line_counter(N) -> Nr is N-1 ; Nr = 0). |
| 67 | | |
| 68 | | inc_line_counter :- |
| 69 | | retract(line_counter(N)), debug_println(5,processed_line(N)), |
| 70 | | N1 is N+1, assertz(line_counter(N1)). |
| 71 | | |
| 72 | | newline --> [10],!, {inc_line_counter}. |
| 73 | | newline --> [13],!, {inc_line_counter}. |
| 74 | | |
| 75 | ? | command(Command) --> "(", ws, !, command1(Command). |
| 76 | | command(exit) --> ":x",!. % for ProB REPL compatibility |
| 77 | | |
| 78 | | |
| 79 | | command1(set_logic(Logic)) --> "set-logic", !, ws, symbol(Logic), close_paren(set_logic). |
| 80 | | command1(set_option(Option)) --> "set-option", !, ws, attribute(Option), close_paren(set_option). |
| 81 | ? | command1(set_info(Info)) --> "set-info", !, ws, attribute(Info), close_paren(set_info(Info)). |
| 82 | | command1(set_info(Info)) --> "meta-info", !, ws, attribute(Info), close_paren(meta_info(Info)). |
| 83 | | command1(reset) --> "reset", !, close_paren(reset). |
| 84 | | command1(declare_sort(Name,Arity)) --> "declare-sort", !, ws, |
| 85 | | symbol(Name), ws, numeral(Arity), close_paren(declare_sort). |
| 86 | | command1(define_sort(Name,Parameters,Sort)) --> "define-sort", !, ws, |
| 87 | | symbol(Name), |
| 88 | | open_paren(define_sort_params(Name)), |
| 89 | | multiple_times(symbol,(Parameters)),close_paren(define_sort_params(Name)), ws, |
| 90 | | must_be_smt_sort(Sort), close_paren(define_sort). |
| 91 | | command1(declare_fun(Name,Parameters,Result)) --> "declare-fun", ws, !, |
| 92 | | must_be_symbol(Name), |
| 93 | | open_paren(declare_fun_params(Name)), |
| 94 | | multiple_times(smt_sort,Parameters), ws, close_paren(declare_fun_params(Name)), ws, |
| 95 | | must_be_smt_sort(Result), |
| 96 | | close_paren(declare_fun). |
| 97 | | command1(declare_fun(Name,[],Result)) --> "declare-const", ws, !, |
| 98 | | must_be_symbol(Name), ws, |
| 99 | | must_be_smt_sort(Result), close_paren(declare_fun). |
| 100 | | command1(define_fun(Name,Parameters,Result,Term)) --> "define-fun", ws, !, |
| 101 | | must_be_symbol(Name), |
| 102 | | open_paren(define_fun_params(Name)), |
| 103 | | multiple_times(sorted_var,Parameters), |
| 104 | | close_paren(define_fun_params(Name)), ws, |
| 105 | | must_be_smt_sort(Result), ws, must_be_term(Term), close_paren(define_fun). |
| 106 | | command1(push(Num)) --> "push", !, ws, numeral(Num), close_paren(push). |
| 107 | | command1(pop(Num)) --> "pop", !, ws, numeral(Num), close_paren(pop). |
| 108 | | command1(assert(Term)) --> "assert", ws, !, |
| 109 | | must_be_term(Term), close_paren(assert). |
| 110 | | command1(check_sat) --> "check-sat", !, close_paren(check_sat). |
| 111 | | command1(get_assert) --> "get-assert", !, close_paren(get_assert). |
| 112 | | command1(get_proof) --> "get-proof", !, close_paren(get_proof). |
| 113 | | command1(get_model) --> "get-model", !, close_paren(get_model). |
| 114 | | command1(get_unsat_core) --> "get-unsat-core", !, close_paren(get_unsat_core). |
| 115 | | command1(get_value(Terms)) --> "get-value", !, open_paren(get_value_arguments), at_least_once(term,Terms), |
| 116 | | close_paren(get_value_arguments), close_paren(get_value). |
| 117 | | command1(get_assign) --> "get-assign", !, close_paren(get_assign). |
| 118 | | command1(get_option(Name)) --> "get-option", ws, !, keyword(Name), close_paren(get_option). |
| 119 | | command1(get_info(Name)) --> "get-info", ws, !, keyword(Name), close_paren(get_info). |
| 120 | | command1(minimize(Term)) --> "minimize", ws, !, must_be_term(Term), close_paren(minimize). |
| 121 | | command1(maximize(Term)) --> "maximize", ws, !, must_be_term(Term), close_paren(minimize). |
| 122 | | command1(exit) --> "exit", !, close_paren(exit). |
| 123 | | % TODO: declare-datatype |
| 124 | | command1(_) --> err_message(command). |
| 125 | | |
| 126 | ? | close_paren(Ctxt) --> real_ws, !, close_paren(Ctxt). |
| 127 | | close_paren(_) --> ")",!. |
| 128 | | close_paren(Ctxt) --> err_message('Missing',closing_parenthesis(Ctxt)). |
| 129 | | |
| 130 | | open_paren(Ctxt) --> real_ws, !, open_paren(Ctxt). |
| 131 | | open_paren(_) --> "(",!. |
| 132 | | open_paren(Ctxt) --> err_message('Missing',opening_parenthesis(Ctxt)). |
| 133 | | |
| 134 | | close_bracket(Ctxt) --> real_ws, !, close_bracket(Ctxt). |
| 135 | | close_bracket(_) --> "]",!. |
| 136 | | close_bracket(Ctxt) --> err_message('Missing',closing_square_bracket(Ctxt)). |
| 137 | | |
| 138 | | |
| 139 | | %command(_,In,_) :- !, atom_codes(A,In), format('Parsing failed on: ~w~n',[A]), !, fail. |
| 140 | | |
| 141 | ? | symbol(Symbol) --> (simplesymbol(Symbol) -> [] ; asciicomment(Symbol)). |
| 142 | ? | simplesymbol(Symbol) --> single_symbol(Start), symbols_or_digits(Rest), {atom_codes(Symbol,[Start|Rest])}. |
| 143 | ? | single_symbol(S) --> [S], {member(S,"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-/*=%?!.$_~&^<>@")}. |
| 144 | ? | single_symbol_or_digit(S) --> single_symbol(S) ; digit(S). |
| 145 | ? | symbols_or_digits([H|T]) --> single_symbol_or_digit(H), symbols_or_digits(T). |
| 146 | | symbols_or_digits([]) --> "". |
| 147 | | |
| 148 | ? | asciicomment(string(Symbol)) --> "|", any_chars_but_pipe(Codes), "|", {atom_codes(Symbol,Codes)}. |
| 149 | | |
| 150 | ? | any_chars_but_pipe([C|T]) --> [C], {C \= 124}, any_chars_but_pipe(T). |
| 151 | | any_chars_but_pipe([]) --> "". |
| 152 | | |
| 153 | ? | any_chars_but_newline([C|T]) --> [C], {C \= 10}, any_chars_but_newline(T). |
| 154 | | any_chars_but_newline([]) --> "". |
| 155 | | |
| 156 | ? | attribute(Attr) --> keyword(A), attribute_aux(Attr,A). |
| 157 | | |
| 158 | ? | attribute_aux(attribute(A,Value),A) --> real_ws, attribute_value(Value),!. |
| 159 | | attribute_aux(attribute(A),A) --> []. |
| 160 | | |
| 161 | ? | attribute_value(Value) --> symbol(Value). |
| 162 | ? | attribute_value(Value) --> spec_constant(Value). |
| 163 | | attribute_value(Value) --> "(", at_least_once(sexpr,Value), ")". |
| 164 | | |
| 165 | | signed_numeral(N) --> "-",!, numeral(N1), {N is -N1}. |
| 166 | ? | signed_numeral(N) --> numeral(N). |
| 167 | | |
| 168 | | numeral(0) --> "0". |
| 169 | | numeral(N) --> digit_no_zero(D1), digits(D), {number_codes(N,[D1|D])}. |
| 170 | | |
| 171 | | digits1([D|T]) --> digit(D),!,digits(T). |
| 172 | | digits([D|T]) --> digit(D),!,digits(T). |
| 173 | | digits([]) --> "". |
| 174 | | hex_digits(Val) --> hex_digits(0,Val). |
| 175 | | hex_digits(ValIn,ValOut) --> hex_digit(_D,ValD),!,{V1 is ValIn*16+ValD}, hex_digits(V1,ValOut). |
| 176 | | hex_digits(Val,Val) --> "". |
| 177 | | |
| 178 | | % hex digits after a point |
| 179 | | hex_digits_after(Val) --> hex_digit(_D,ValD),!, hex_digits_after(ValT), {Val is ValT/16+ValD}. |
| 180 | | hex_digits_after(0.0) --> "". |
| 181 | | |
| 182 | | signed_decimal_numeral(D) --> "-", !, decimal_numeral(D1), {D is -D1}. |
| 183 | | signed_decimal_numeral(N) --> decimal_numeral(N). |
| 184 | ? | decimal_numeral(DResult) --> numeral(D1), ".", !, digits1(DC2), decimal_exponent(DE), |
| 185 | | {number_codes(D1,DC1), |
| 186 | | append([DC1,".",DC2,DE],Codes), |
| 187 | | number_codes(DResult,Codes)}. |
| 188 | | decimal_numeral(DResult) --> ("0x" ; "+0x"),hex_digits(Val1), hex_numeral_rest1(Val1,DResult). |
| 189 | | |
| 190 | | decimal_exponent([0'e,S|D]) --> ("e" ; "E"), exp_sign(S), !,digits1(D). |
| 191 | | decimal_exponent([]) --> "". |
| 192 | | exp_sign(D) --> [D], {D = 0'- ; D=0'+},!. |
| 193 | | exp_sign(0'+) --> "". |
| 194 | | |
| 195 | | hex_numeral_rest1(V1,DResult) --> ".",hex_digits_after(V2), !, {V is V1+V2},hex_numeral_rest2(V,DResult). |
| 196 | | hex_numeral_rest1(V1,DResult) --> hex_numeral_rest2(V1,DResult). |
| 197 | | hex_numeral_rest2(V,DResult) --> "p+",digits1(ExpS),!, |
| 198 | | {number_codes(Exp,ExpS), DResult is V*2^Exp}. % TODO: process hexadecimal literal |
| 199 | | hex_numeral_rest2(V,V) --> "". |
| 200 | | |
| 201 | | digit(D) --> [D], {D >= 48, D =< 57}. |
| 202 | | digit_no_zero(D) --> [D], {D >= 49, D =< 57}. |
| 203 | | hex_digit(D,Val) --> [D], {D >= 48, (D =< 57, Val is D-38 ; D >= 65, D =< 70, Val is D-65)}. |
| 204 | | |
| 205 | | smt_sort(sort(Sort)) --> identifier(SortName),!,optional_range(SortName,Sort). |
| 206 | | smt_sort(sort(S)) --> numeral(S). % not yet supported in Translation, what does it mean? |
| 207 | | smt_sort(sort('BitVec',[Length])) --> "(_ BitVec ", numeral(Length), ")". |
| 208 | ? | smt_sort(Sort) --> "(", !, smt_sort_after_paren(Sort). |
| 209 | | |
| 210 | | smt_sort_after_paren(sort(smt_fun(Params))) --> "->", !, % something like (-> Int Bool) |
| 211 | | ws, at_least_once(smt_sort,Params), close_paren(sort). |
| 212 | ? | smt_sort_after_paren(sort(S,Params)) --> identifier(S), ws, at_least_once(smt_sort,Params), close_paren(sort). |
| 213 | | |
| 214 | | % like smt_sort, but throw error message: |
| 215 | ? | must_be_smt_sort(T) --> smt_sort(T),!. |
| 216 | | must_be_smt_sort(_) --> err_message(sort). |
| 217 | | |
| 218 | | % integer or real range [0, 5] or default value [0.5] |
| 219 | | % these are model annotations for preferred values, but do not force any constraint |
| 220 | | optional_range(Sort,ResultingSort) --> real_ws,!,optional_range(Sort,ResultingSort). |
| 221 | | optional_range(Sort,Sort) --> % should be sort('Int') or sort('Real'); |
| 222 | | % these return range in type and add constraints in interpreter |
| 223 | | "[", !, |
| 224 | | signed_number(range,From), |
| 225 | | range_list_rest(Rest), |
| 226 | | close_bracket(range), |
| 227 | | {add_message(smtlib_parser,'Ignoring default range declaration: ',[From|Rest])}. % TODO: line_counter(N) |
| 228 | | optional_range(Sort,Sort) --> "". |
| 229 | | |
| 230 | | range_list_rest([H|T]) --> comma(range), !, signed_number(range,H), range_list_rest(T). |
| 231 | | range_list_rest([]) --> "". |
| 232 | | |
| 233 | | comma(Ctxt) --> real_ws, !, comma(Ctxt). |
| 234 | | comma(_) --> ",",!. |
| 235 | | |
| 236 | | signed_number(Ctxt,N) --> real_ws, !, signed_number(Ctxt,N). |
| 237 | | signed_number(_,N) --> signed_decimal_numeral(N),!. |
| 238 | | signed_number(_,N) --> signed_numeral(N),!. |
| 239 | | signed_number(Ctxt,_) --> err_message('Missing',number(Ctxt)). |
| 240 | | |
| 241 | | sorted_var(S) --> real_ws,!, sorted_var(S). |
| 242 | ? | sorted_var(svar(Name,Sort)) --> "(", symbol(Name), ws, smt_sort(Sort), ")". |
| 243 | | |
| 244 | | term(let(Bindings,Term)) --> |
| 245 | ? | "(let", ws, !, "(", at_least_once(binding,Bindings), ")", ws, !, must_be_term(Term), !, close_paren(let). |
| 246 | | term(forall(Vars,Term)) --> |
| 247 | | "(forall", ws, !, "(", at_least_once(sorted_var,Vars), ")", ws, !, must_be_term(Term), !, close_paren(forall). |
| 248 | | term(exists(Vars,Term)) --> |
| 249 | | "(exists", ws, !, "(", at_least_once(sorted_var,Vars), ")", ws, !, must_be_term(Term), !, close_paren(exists). |
| 250 | ? | term(T) --> spec_constant(T),!. |
| 251 | ? | term(T) --> qual_identifier(T),!. |
| 252 | | term(attributed_term(Term,Attributes)) --> "(", "!", ws, !, |
| 253 | | must_be_term(Term), ws, !, |
| 254 | | at_least_once(attribute,Attributes), close_paren(attributed_term). |
| 255 | ? | term(id_term(Id,Terms)) --> "(", ws, qual_identifier(Id), ws, !, at_least_once(term,Terms), close_paren(id_term). |
| 256 | | |
| 257 | | % term which raises error if it cannot be found |
| 258 | ? | must_be_term(T) --> term(T),!. |
| 259 | | must_be_term(_) --> err_message(term). |
| 260 | | |
| 261 | | binding(bind(Symbol,Term)) --> "(", must_be_symbol(Symbol), ws, !, must_be_term(Term), close_paren(bind). |
| 262 | | % binding which raises error if it cannot be found |
| 263 | | must_be_symbol(T) --> symbol(T),!. |
| 264 | | must_be_symbol(_) --> err_message(symbol). |
| 265 | | |
| 266 | | qual_identifier(id(Id)) --> identifier(Id). |
| 267 | | qual_identifier(id(Id,Sort)) --> "(as ", ws, !, identifier(Id), ws, smt_sort(Sort), ")". |
| 268 | | qual_identifier(id(sign_extend(Parameter))) --> "(_ sign_extend ", !, numeral(Parameter), ")". |
| 269 | | qual_identifier(id(zero_extend(Parameter))) --> "(_ zero_extend ", !, numeral(Parameter), ")". |
| 270 | | qual_identifier(id(extract(P1,P2))) --> "(_ extract ", !, numeral(P1), ws, numeral(P2), ")". |
| 271 | | |
| 272 | | |
| 273 | | identifier(Id) --> symbol(Id). |
| 274 | | %identifier(id_num(Id,Num)) --> "(", "_", symbol(Id), numeral(Num), ")". |
| 275 | | |
| 276 | ? | keyword(KW) --> ":", symbols_or_digits(L), {atom_codes(KW,L)}. |
| 277 | | |
| 278 | | sexpr(E) --> spec_constant(E). |
| 279 | | sexpr(E) --> symbol(E). |
| 280 | | sexpr(E) --> keyword(E). |
| 281 | | sexpr(E) --> "(", multiple_times(sexpr,E), ")". |
| 282 | | |
| 283 | | spec_constant(decimal(D)) --> signed_decimal_numeral(D). |
| 284 | ? | spec_constant(integer(I)) --> signed_numeral(I). |
| 285 | ? | spec_constant(string(S)) --> "\"", any_chars_but_quotations(SCodes), "\"", {atom_codes(S,SCodes)}. |
| 286 | | spec_constant(bitvector(Val,Length)) --> "(_ bv", numeral(Val), " ", numeral(Length), ")". |
| 287 | | spec_constant(bitvector(Binary)) --> "#b", binary_numbers(Binary). |
| 288 | | |
| 289 | | binary_numbers([H|T]) --> binary_number(H), binary_numbers(T). |
| 290 | | binary_numbers([N]) --> binary_number(N). |
| 291 | | binary_number(0) --> "0". |
| 292 | | binary_number(1) --> "1". |
| 293 | | |
| 294 | ? | any_chars_but_quotations([C|T]) --> [C], {C \= 34}, any_chars_but_quotations(T). |
| 295 | | % two quotations are allowed in smtlib 2.5 |
| 296 | | any_chars_but_quotations([C,C|T]) --> [C,C], {C = 34}, any_chars_but_quotations(T). |
| 297 | | any_chars_but_quotations([]) --> "". |
| 298 | | |
| 299 | | % whitespaces |
| 300 | | ws --> " ", !, ws. |
| 301 | | ws --> [9], !, ws. % tab |
| 302 | | ws --> newline, !, ws. |
| 303 | ? | ws --> comment(_),!,ws. |
| 304 | | ws --> "". |
| 305 | | |
| 306 | | real_ws --> " ", !, ws. |
| 307 | | real_ws --> [9], !, ws. |
| 308 | | real_ws --> newline, ws. |
| 309 | ? | real_ws --> comment(_),!,ws. |
| 310 | | |
| 311 | ? | comment(Chars) --> ";", any_chars_but_newline(Chars), newline. |