├── LICENSE ├── README.md ├── ex1.pl ├── ex2.pl ├── swiplz3.c └── swiplz3.pl /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 The MiST group (Multi-paradigm Software Technology) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SWIPrologZ3 2 | 3 | ### A simple SWI Prolog API for Microsoft's SMT solver Z3 4 | 5 | 6 | First, you should install SWI Prolog and the SMT solver Z3. It has been tested with SWI Prolog version 7.6.3 and Z3 version 4.6.1. 7 | 8 | Then, you can download or clone the repository, e.g., 9 | 10 | ````$ git clone https://github.com/mistupv/SWIPrologZ3.git```` 11 | 12 | and compile the C source file using the SWI Prolog utility program ````swipl-ld````, as follows: 13 | 14 | ````$ swipl-ld -c swiplz3.c```` 15 | 16 | ````$ swipl-ld -shared -o swiplz3 swiplz3.o -lz3```` 17 | 18 | Now, in order to use the Z3 functions, your Prolog code should load the file ```swiplz3.pl```. For this purpose, you can add 19 | 20 | ````:- use_module(swiplz3).```` 21 | 22 | to your Prolog file. 23 | 24 | Check the simple examples ```ex1.pl``` and ```ex2.pl```. 25 | 26 | This is ongoing work. Currently, it only covers constraints over integers and a few basic Z3 functions. 27 | 28 | Please send me any comment to -------------------------------------------------------------------------------- /ex1.pl: -------------------------------------------------------------------------------- 1 | /* very simple example showing the functionality of the Z3 functions */ 2 | 3 | :- use_module(swiplz3). 4 | 5 | main :- 6 | z3_mk_config, 7 | z3_set_param_value("model","true"), 8 | z3_mk_context(N), 9 | z3_mk_solver(N), 10 | z3_del_config, 11 | z3_push(N), 12 | /* first constraint */ 13 | C1 = [X>0,X<10], 14 | z3_intconstr2smtlib(N,[],C1,VarsC1,C1smtlib2), 15 | (VarsC1=[] -> true ; z3_mk_int_vars(N,VarsC1)), 16 | z3_assert_string(N,C1smtlib2), 17 | /* second constraint */ 18 | C2 = [Y>0], 19 | z3_intconstr2smtlib(N,C1,C2,VarsC2,C2smtlib2), 20 | (VarsC2=[] -> true ; z3_mk_int_vars(N,VarsC2)), 21 | z3_assert_string(N,C2smtlib2), 22 | /* third constraint */ 23 | C3 = [X>Y], 24 | z3_intconstr2smtlib(N,(C1,C2),C3,VarsC3,C3smtlib2), 25 | (VarsC3=[] -> true ; z3_mk_int_vars(N,VarsC3)), 26 | z3_assert_string(N,C3smtlib2), 27 | /* checking satisfiability */ 28 | (z3_check(N) -> 29 | z3_print_model(N), 30 | get_context_vars(N,VVS), 31 | get_model_var_eval(N,VVS,Values), 32 | %% nl,format("Variables: "),print(VVS), 33 | %% nl,format("Values: "),print(Values), 34 | term_variables((C1,C2,C3),AllVars), 35 | AllVars=Values, 36 | format("Solved formulas: "),print((C1,C2,C3)), 37 | nl 38 | ; 39 | true 40 | ), 41 | z3_pop(N), 42 | z3_del_solver(N), 43 | z3_del_context(N). 44 | 45 | 46 | -------------------------------------------------------------------------------- /ex2.pl: -------------------------------------------------------------------------------- 1 | /* a very simple clp interpreter over integers using the Z3 SMT solver */ 2 | 3 | :- use_module(swiplz3). 4 | 5 | :- dynamic context/1. 6 | 7 | init :- 8 | z3_mk_config, 9 | z3_set_param_value("model","true"), 10 | z3_mk_context(N), 11 | assert(context(N)), 12 | z3_mk_solver(N), 13 | z3_del_config. 14 | 15 | solve(true,C,C). 16 | solve((A,B),C,CB):- 17 | solve(A,C,CA),solve(B,CA,CB). 18 | solve(A,C,CA):- 19 | clp_clause(A,[],B), 20 | solve(B,C,CA). 21 | solve(A,C,CC):- 22 | clp_clause(A,CA,B),CA\=[], 23 | /* constraint solving using Z3 */ 24 | context(N), 25 | z3_intconstr2smtlib(N,C,CA,VarsC,Csmtlib2), 26 | (VarsC=[] -> true ; z3_mk_int_vars(N,VarsC)), 27 | z3_assert_string(N,Csmtlib2),z3_check(N), 28 | /* end */ 29 | append(C,CA,CC_), 30 | solve(B,CC_,CC). 31 | 32 | 33 | /* a concrete example */ 34 | 35 | clp_clause(list_length([],Length),[Length=0],true). 36 | clp_clause(list_length([_|Ls], Length),[Length = Length0 + 1],list_length(Ls, Length0)). 37 | 38 | /* you can test a goal using solve/1 */ 39 | 40 | solve(G) :- 41 | init, 42 | solve(G,[],C), 43 | /* producing a solution */ 44 | context(N), 45 | get_context_vars(N,VVS), 46 | get_model_var_eval(N,VVS,Values), 47 | term_variables(C,AllVars), 48 | AllVars=Values. 49 | 50 | /* 51 | try, e.g., ?- solve(list_length([1,2,3],L)). 52 | */ 53 | 54 | -------------------------------------------------------------------------------- /swiplz3.c: -------------------------------------------------------------------------------- 1 | // 2 | // aux.c 3 | // 4 | // 5 | // Created by German Vidal on 24/01/2018. 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define LOG_Z3_CALLS 17 | 18 | #ifdef LOG_Z3_CALLS 19 | #define LOG_MSG(msg) Z3_append_log(msg) 20 | #else 21 | #define LOG_MSG(msg) ((void)0) 22 | #endif 23 | 24 | #define MAXCONS 64 25 | #define MAXVARS 256 26 | 27 | /* Some global variables */ 28 | 29 | Z3_config cfg; //we consider a single configuration 30 | Z3_context ctx[MAXCONS]; //but MAXCONS different contexts 31 | Z3_solver z3s[MAXCONS]; //and solvers 32 | 33 | Z3_symbol names[MAXCONS][MAXVARS]; 34 | Z3_func_decl decls[MAXCONS][MAXVARS]; 35 | int numvar[MAXCONS] = { 0 }; /* current number of variables in each context */ 36 | 37 | long cur = 0; /* current context */ 38 | 39 | /** 40 | \brief exit gracefully in case of error. 41 | */ 42 | void exitf(const char* message) 43 | { 44 | fprintf(stderr,"BUG: %s.\n", message); 45 | exit(1); 46 | } 47 | 48 | /** 49 | \brief exit if unreachable code was reached. 50 | */ 51 | void unreachable() 52 | { 53 | exitf("unreachable code was reached"); 54 | } 55 | 56 | 57 | /***************************************************/ 58 | /* some pretty printing */ 59 | /***************************************************/ 60 | 61 | /** 62 | \brief Display a symbol in the given output stream. 63 | */ 64 | void display_symbol(Z3_context c, FILE * out, Z3_symbol s) 65 | { 66 | switch (Z3_get_symbol_kind(c, s)) { 67 | case Z3_INT_SYMBOL: 68 | fprintf(out, "#%d", Z3_get_symbol_int(c, s)); 69 | break; 70 | case Z3_STRING_SYMBOL: 71 | fprintf(out, "%s", Z3_get_symbol_string(c, s)); 72 | break; 73 | default: 74 | unreachable(); 75 | } 76 | } 77 | 78 | /** 79 | \brief Display the given type. 80 | */ 81 | void display_sort(Z3_context c, FILE * out, Z3_sort ty) 82 | { 83 | switch (Z3_get_sort_kind(c, ty)) { 84 | case Z3_UNINTERPRETED_SORT: 85 | display_symbol(c, out, Z3_get_sort_name(c, ty)); 86 | break; 87 | case Z3_BOOL_SORT: 88 | fprintf(out, "bool"); 89 | break; 90 | case Z3_INT_SORT: 91 | fprintf(out, "int"); 92 | break; 93 | case Z3_REAL_SORT: 94 | fprintf(out, "real"); 95 | break; 96 | case Z3_BV_SORT: 97 | fprintf(out, "bv%d", Z3_get_bv_sort_size(c, ty)); 98 | break; 99 | case Z3_ARRAY_SORT: 100 | fprintf(out, "["); 101 | display_sort(c, out, Z3_get_array_sort_domain(c, ty)); 102 | fprintf(out, "->"); 103 | display_sort(c, out, Z3_get_array_sort_range(c, ty)); 104 | fprintf(out, "]"); 105 | break; 106 | case Z3_DATATYPE_SORT: 107 | if (Z3_get_datatype_sort_num_constructors(c, ty) != 1) 108 | { 109 | fprintf(out, "%s", Z3_sort_to_string(c,ty)); 110 | break; 111 | } 112 | { 113 | unsigned num_fields = Z3_get_tuple_sort_num_fields(c, ty); 114 | unsigned i; 115 | fprintf(out, "("); 116 | for (i = 0; i < num_fields; i++) { 117 | Z3_func_decl field = Z3_get_tuple_sort_field_decl(c, ty, i); 118 | if (i > 0) { 119 | fprintf(out, ", "); 120 | } 121 | display_sort(c, out, Z3_get_range(c, field)); 122 | } 123 | fprintf(out, ")"); 124 | break; 125 | } 126 | default: 127 | fprintf(out, "unknown["); 128 | display_symbol(c, out, Z3_get_sort_name(c, ty)); 129 | fprintf(out, "]"); 130 | break; 131 | } 132 | } 133 | 134 | /** 135 | \brief Custom ast pretty printer. 136 | 137 | This function demonstrates how to use the API to navigate terms. 138 | */ 139 | void display_ast(Z3_context c, FILE * out, Z3_ast v) 140 | { 141 | switch (Z3_get_ast_kind(c, v)) { 142 | case Z3_NUMERAL_AST: { 143 | Z3_sort t; 144 | fprintf(out, "%s", Z3_get_numeral_string(c, v)); 145 | t = Z3_get_sort(c, v); 146 | fprintf(out, ":"); 147 | display_sort(c, out, t); 148 | break; 149 | } 150 | case Z3_APP_AST: { 151 | unsigned i; 152 | Z3_app app = Z3_to_app(c, v); 153 | unsigned num_fields = Z3_get_app_num_args(c, app); 154 | Z3_func_decl d = Z3_get_app_decl(c, app); 155 | fprintf(out, "%s", Z3_func_decl_to_string(c, d)); 156 | if (num_fields > 0) { 157 | fprintf(out, "["); 158 | for (i = 0; i < num_fields; i++) { 159 | if (i > 0) { 160 | fprintf(out, ", "); 161 | } 162 | display_ast(c, out, Z3_get_app_arg(c, app, i)); 163 | } 164 | fprintf(out, "]"); 165 | } 166 | break; 167 | } 168 | case Z3_QUANTIFIER_AST: { 169 | fprintf(out, "quantifier"); 170 | ; 171 | } 172 | default: 173 | fprintf(out, "#unknown"); 174 | } 175 | } 176 | 177 | /** 178 | \brief Custom function interpretations pretty printer. 179 | */ 180 | void display_function_interpretations(Z3_context c, FILE * out, Z3_model m) 181 | { 182 | unsigned num_functions, i; 183 | 184 | fprintf(out, "function interpretations:\n"); 185 | 186 | num_functions = Z3_model_get_num_funcs(c, m); 187 | for (i = 0; i < num_functions; i++) { 188 | Z3_func_decl fdecl; 189 | Z3_symbol name; 190 | Z3_ast func_else; 191 | unsigned num_entries = 0, j; 192 | Z3_func_interp_opt finterp; 193 | 194 | fdecl = Z3_model_get_func_decl(c, m, i); 195 | finterp = Z3_model_get_func_interp(c, m, fdecl); 196 | Z3_func_interp_inc_ref(c, finterp); 197 | name = Z3_get_decl_name(c, fdecl); 198 | display_symbol(c, out, name); 199 | fprintf(out, " = {"); 200 | if (finterp) 201 | num_entries = Z3_func_interp_get_num_entries(c, finterp); 202 | for (j = 0; j < num_entries; j++) { 203 | unsigned num_args, k; 204 | Z3_func_entry fentry = Z3_func_interp_get_entry(c, finterp, j); 205 | Z3_func_entry_inc_ref(c, fentry); 206 | if (j > 0) { 207 | fprintf(out, ", "); 208 | } 209 | num_args = Z3_func_entry_get_num_args(c, fentry); 210 | fprintf(out, "("); 211 | for (k = 0; k < num_args; k++) { 212 | if (k > 0) { 213 | fprintf(out, ", "); 214 | } 215 | display_ast(c, out, Z3_func_entry_get_arg(c, fentry, k)); 216 | } 217 | fprintf(out, "|->"); 218 | display_ast(c, out, Z3_func_entry_get_value(c, fentry)); 219 | fprintf(out, ")"); 220 | Z3_func_entry_dec_ref(c, fentry); 221 | } 222 | if (num_entries > 0) { 223 | fprintf(out, ", "); 224 | } 225 | fprintf(out, "(else|->"); 226 | func_else = Z3_func_interp_get_else(c, finterp); 227 | display_ast(c, out, func_else); 228 | fprintf(out, ")}\n"); 229 | Z3_func_interp_dec_ref(c, finterp); 230 | } 231 | } 232 | 233 | /** 234 | \brief Custom model pretty printer. 235 | */ 236 | void display_model(Z3_context c, FILE * out, Z3_model m) 237 | { 238 | unsigned num_constants; 239 | unsigned i; 240 | 241 | if (!m) return; 242 | 243 | num_constants = Z3_model_get_num_consts(c, m); 244 | for (i = 0; i < num_constants; i++) { 245 | Z3_symbol name; 246 | Z3_func_decl cnst = Z3_model_get_const_decl(c, m, i); 247 | Z3_ast a, v; 248 | Z3_bool ok; 249 | name = Z3_get_decl_name(c, cnst); 250 | display_symbol(c, out, name); 251 | fprintf(out, " = "); 252 | a = Z3_mk_app(c, cnst, 0, 0); 253 | v = a; 254 | ok = Z3_model_eval(c, m, a, 1, &v); 255 | display_ast(c, out, v); 256 | fprintf(out, "\n"); 257 | } 258 | display_function_interpretations(c, out, m); 259 | } 260 | 261 | 262 | 263 | /***************************************************/ 264 | /* make a new context */ 265 | /***************************************************/ 266 | 267 | /** 268 | \brief Simpler error handler. 269 | */ 270 | void error_handler(Z3_context c, Z3_error_code e) 271 | { 272 | /* printf("Error code: %d\n", e); 273 | exitf("incorrect use of Z3"); */ 274 | char *error = NULL; 275 | 276 | switch(e) 277 | { 278 | case Z3_OK: 279 | error = "Z3_OK"; 280 | break; 281 | case Z3_SORT_ERROR: 282 | error = "Z3_SORT_ERROR"; 283 | break; 284 | case Z3_IOB: 285 | error = "Z3_IOB"; 286 | break; 287 | case Z3_INVALID_ARG: 288 | error = "Z3_INVALID_ARG"; 289 | break; 290 | case Z3_PARSER_ERROR: 291 | error = "Z3_PARSER_ERROR"; 292 | break; 293 | case Z3_NO_PARSER: 294 | error = "Z3_NO_PARSER"; 295 | break; 296 | case Z3_INVALID_PATTERN: 297 | error = "Z3_INVALID_PATTERN"; 298 | break; 299 | case Z3_MEMOUT_FAIL: 300 | error = "Z3_MEMOUT_FAIL"; 301 | break; 302 | case Z3_FILE_ACCESS_ERROR: 303 | error = "Z3_FILE_ACCESS_ERROR"; 304 | break; 305 | case Z3_INTERNAL_FATAL: 306 | error = "Z3_INTERNAL_FATAL"; 307 | break; 308 | case Z3_INVALID_USAGE: 309 | error = "Z3_INVALID_USAGE"; 310 | break; 311 | case Z3_DEC_REF_ERROR: 312 | error = "Z3_DEC_REF_ERROR"; 313 | break; 314 | case Z3_EXCEPTION: 315 | error = "Z3_EXCEPTION"; 316 | break; 317 | default: 318 | error = "Z3 BUG: unknown error"; 319 | } 320 | 321 | term_t t1 = PL_new_term_ref(); 322 | t1 = PL_new_functor(PL_new_atom(error), 0); 323 | PL_raise_exception(t1); /* raise the exception */ 324 | } 325 | 326 | /** 327 | Create a configuration 328 | */ 329 | static foreign_t pl_mk_config() 330 | { 331 | cfg = Z3_mk_config(); 332 | return 1; 333 | } 334 | 335 | /** 336 | Delete a configuration 337 | */ 338 | static foreign_t pl_del_config() 339 | { 340 | Z3_del_config(cfg); 341 | return 1; 342 | } 343 | 344 | /** 345 | Set a configuration parameter 346 | */ 347 | static foreign_t pl_set_param_value(term_t param, term_t value) 348 | { 349 | char *par,*val; 350 | if (!PL_get_chars(param,&par,CVT_STRING)) 351 | return PL_warning("z3_set_parameter_value/1: instantiation fault (parameter)"); 352 | if (!PL_get_chars(value,&val,CVT_STRING)) 353 | return PL_warning("z3_set_parameter_value/1: instantiation fault (value)"); 354 | 355 | Z3_set_param_value(cfg,par,val); 356 | return 1; 357 | } 358 | 359 | /** 360 | Create a context with index ind using the current configuration 361 | Enable tracing to stderr and register standard error handler. 362 | */ 363 | static foreign_t pl_mk_context(term_t ind) 364 | { 365 | int rval; 366 | 367 | if (cur true 35 | ; 36 | assert_vars(Context,NewVarsStr) 37 | ), 38 | constr2smt(CC,SMT_), 39 | string_codes(SMT,SMT_),!. 40 | 41 | /* 42 | z3_assert_string/2 takes an SMT formula and asserts it to a context 43 | */ 44 | 45 | z3_assert_string(N,SMT) :- 46 | string_codes(SMT,SMTC), 47 | string_codes("(assert ",S1), 48 | string_codes(" )",S2), 49 | append(S1,SMTC,S3), 50 | append(S3,S2,SMTLIB2_), 51 | string_codes(SMTLIB2,SMTLIB2_), 52 | z3_assert_string_(N,SMTLIB2). 53 | 54 | 55 | assert_vars(_,[]). 56 | assert_vars(N,[X|R]) :- 57 | assertz(var(N,X)), 58 | assert_vars(N,R). 59 | 60 | get_context_vars(N,VVS) :- 61 | findall(VV,var(N,VV),VVS). 62 | 63 | get_model_var_eval(_N,[],[]) :- !. 64 | get_model_var_eval(N,[Var|R],[Val|RR]) :- 65 | z3_get_model_intvar_eval(N,Var,Val), 66 | get_model_var_eval(N,R,RR). 67 | 68 | var_decl([],[]). 69 | var_decl([V|R],SS) :- 70 | string_codes("(declare-const ",S1), 71 | write_to_chars(V,Var), 72 | append(S1,Var,S2), 73 | string_codes(" Int) ",S3), 74 | append(S2,S3,S), 75 | var_decl(R,RS), 76 | append(S,RS,SS). 77 | 78 | /* 79 | z3_solver_assert_int(N,C) :- 80 | term_variables(C,CVars), 81 | numbervars(CVars),get_varnames(CVars,CVarsNames) 82 | mk_int_var_list(N,CVarsNames), 83 | clp2smt(C,SMT),!, 84 | append([40,97,115,115,101,114,116,32|SMT],[41], SMT1), %(assert SMT) 85 | string_codes(SMT2,SMT1), 86 | z3_assert_string(N,SMT2). 87 | */ 88 | 89 | /* 90 | constr2smt/2 translates a list of simple integer constraints (>,<,=,\=,>=,=< and +,-,*,div,mod,rem) 91 | to a list of codes representing an SMTLIB2 string 92 | */ 93 | 94 | constr2smt([C],SMT) :- 95 | !,con2smt(C,SMT). 96 | constr2smt(List,SMT) :- 97 | con2smt_list(List,SMT_), 98 | string_codes("(and ",S1), 99 | append(S1,SMT_,S2), 100 | string_codes(")",S3), 101 | append(S2,S3,SMT). 102 | 103 | con2smt_list([C],SMT) :- 104 | !,con2smt(C,SMT). 105 | con2smt_list([C|R],SMT) :- 106 | con2smt(C,SMT1), 107 | con2smt_list(R,SMTR), 108 | string_codes(" ",Blank), 109 | append(SMT1,Blank,S), 110 | append(S,SMTR,SMT). 111 | 112 | /* expression rooted by a binary operator */ 113 | con2smt(T,SMT) :- 114 | functor(T,F,2),!,transf(F,S1,S2), 115 | arg(1,T,Arg1),con2smt(Arg1,SMT1), 116 | arg(2,T,Arg2),con2smt(Arg2,SMT2), 117 | string_codes(" ",Blank), 118 | append(S1,SMT1,S),append(S,Blank,S_), 119 | append(S_,SMT2,S__),append(S__,S2,SMT). 120 | 121 | /* negative number */ 122 | con2smt(T,SMT) :- 123 | functor(T,-,1),!,transf(-,S1,S2), 124 | arg(1,T,Arg1),con2smt(Arg1,SMT1), 125 | append(S1,SMT1,S),append(S,S2,SMT). 126 | 127 | /* integer */ 128 | con2smt(T,SMT) :- 129 | integer(T),!, 130 | atom_codes(T,SMT). 131 | 132 | /* variable */ 133 | con2smt(T,SMT) :- 134 | functor(T,'$VAR',1),!, 135 | write_to_chars(T,SMT). 136 | 137 | /* unsupported term */ 138 | con2smt(T,_SMT) :- 139 | throw(unsupported_constraint(T)). 140 | 141 | 142 | /* binary operators */ 143 | transf(>,S1,S2) :- string_codes("(> ",S1),string_codes(")",S2). 144 | transf(<,S1,S2) :- string_codes("(< ",S1),string_codes(")",S2). 145 | transf(>=,S1,S2) :- string_codes("(>= ",S1),string_codes(")",S2). 146 | transf(=<,S1,S2) :- string_codes("(<= ",S1),string_codes(")",S2). 147 | transf(=,S1,S2) :- string_codes("(= ",S1),string_codes(")",S2). 148 | transf(\=,S1,S2) :- string_codes("(not (= ",S1),string_codes("))",S2). 149 | transf(*,S1,S2) :- string_codes("(* ",S1),string_codes(")",S2). 150 | transf(+,S1,S2) :- string_codes("(+ ",S1),string_codes(")",S2). 151 | transf(-,S1,S2) :- string_codes("(- ",S1),string_codes(")",S2). 152 | transf(div,S1,S2) :- string_codes("(div ",S1),string_codes(")",S2). 153 | transf(mod,S1,S2) :- string_codes("(mod ",S1),string_codes(")",S2). 154 | transf(rem,S1,S2) :- string_codes("(rem ",S1),string_codes(")",S2). 155 | 156 | /* unary operators */ 157 | transf(-,S1,S2) :- string_codes("(- ",S1),string_codes(")",S2). 158 | 159 | --------------------------------------------------------------------------------