| author | wenzelm | 
| Wed, 15 Oct 1997 15:12:59 +0200 | |
| changeset 3872 | a5839ecee7b8 | 
| parent 3408 | 98a2d517cabe | 
| child 3893 | 5a1f22e7b359 | 
| permissions | -rw-r--r-- | 
| 1460 | 1 | (* Title: Pure/logic.ML | 
| 0 | 2 | ID: $Id$ | 
| 1460 | 3 | Author: Lawrence C Paulson, Cambridge University Computer Laboratory | 
| 0 | 4 | Copyright Cambridge University 1992 | 
| 5 | ||
| 6 | Supporting code for defining the abstract type "thm" | |
| 7 | *) | |
| 8 | ||
| 9 | infix occs; | |
| 10 | ||
| 11 | signature LOGIC = | |
| 12 | sig | |
| 1460 | 13 | val assum_pairs : term -> (term*term)list | 
| 14 | val auto_rename : bool ref | |
| 15 | val close_form : term -> term | |
| 16 | val count_prems : term * int -> int | |
| 17 | val dest_equals : term -> term * term | |
| 18 | val dest_flexpair : term -> term * term | |
| 19 | val dest_implies : term -> term * term | |
| 20 | val dest_inclass : term -> typ * class | |
| 21 | val dest_type : term -> typ | |
| 22 | val flatten_params : int -> term -> term | |
| 23 | val incr_indexes : typ list * int -> term -> term | |
| 24 | val lift_fns : term * int -> (term -> term) * (term -> term) | |
| 25 | val list_flexpairs : (term*term)list * term -> term | |
| 26 | val list_implies : term list * term -> term | |
| 0 | 27 | val list_rename_params: string list * term -> term | 
| 637 | 28 | val is_equals : term -> bool | 
| 1460 | 29 | val mk_equals : term * term -> term | 
| 30 | val mk_flexpair : term * term -> term | |
| 31 | val mk_implies : term * term -> term | |
| 32 | val mk_inclass : typ * class -> term | |
| 33 | val mk_type : typ -> term | |
| 34 | val occs : term * term -> bool | |
| 35 | val rule_of : (term*term)list * term list * term -> term | |
| 36 | val set_rename_prefix : string -> unit | |
| 37 | val skip_flexpairs : term -> term | |
| 0 | 38 | val strip_assums_concl: term -> term | 
| 1460 | 39 | val strip_assums_hyp : term -> term list | 
| 40 | val strip_flexpairs : term -> (term*term)list * term | |
| 41 | val strip_horn : term -> (term*term)list * term list * term | |
| 42 | val strip_imp_concl : term -> term | |
| 43 | val strip_imp_prems : term -> term list | |
| 44 | val strip_params : term -> (string * typ) list | |
| 45 | val strip_prems : int * term list * term -> term list * term | |
| 2508 | 46 | val unvarify : term -> term | 
| 47 | val varify : term -> term | |
| 48 | val termord : term * term -> order | |
| 49 | val lextermord : term list * term list -> order | |
| 50 | val termless : term * term -> bool | |
| 0 | 51 | end; | 
| 52 | ||
| 1500 | 53 | structure Logic : LOGIC = | 
| 0 | 54 | struct | 
| 398 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 55 | |
| 0 | 56 | (*** Abstract syntax operations on the meta-connectives ***) | 
| 57 | ||
| 58 | (** equality **) | |
| 59 | ||
| 1835 | 60 | (*Make an equality. DOES NOT CHECK TYPE OF u*) | 
| 64 
0bbe5d86cb38
logic/mk_equals,mk_flexpair: now calls fastype_of instead of type_of.
 lcp parents: 
0diff
changeset | 61 | fun mk_equals(t,u) = equals(fastype_of t) $ t $ u; | 
| 0 | 62 | |
| 63 | fun dest_equals (Const("==",_) $ t $ u)  =  (t,u)
 | |
| 64 |   | dest_equals t = raise TERM("dest_equals", [t]);
 | |
| 65 | ||
| 637 | 66 | fun is_equals (Const ("==", _) $ _ $ _) = true
 | 
| 67 | | is_equals _ = false; | |
| 68 | ||
| 69 | ||
| 0 | 70 | (** implies **) | 
| 71 | ||
| 72 | fun mk_implies(A,B) = implies $ A $ B; | |
| 73 | ||
| 74 | fun dest_implies (Const("==>",_) $ A $ B)  =  (A,B)
 | |
| 75 |   | dest_implies A = raise TERM("dest_implies", [A]);
 | |
| 76 | ||
| 77 | (** nested implications **) | |
| 78 | ||
| 79 | (* [A1,...,An], B goes to A1==>...An==>B *) | |
| 80 | fun list_implies ([], B) = B : term | |
| 81 | | list_implies (A::AS, B) = implies $ A $ list_implies(AS,B); | |
| 82 | ||
| 83 | (* A1==>...An==>B goes to [A1,...,An], where B is not an implication *) | |
| 84 | fun strip_imp_prems (Const("==>", _) $ A $ B) = A :: strip_imp_prems B
 | |
| 85 | | strip_imp_prems _ = []; | |
| 86 | ||
| 87 | (* A1==>...An==>B goes to B, where B is not an implication *) | |
| 88 | fun strip_imp_concl (Const("==>", _) $ A $ B) = strip_imp_concl B
 | |
| 89 | | strip_imp_concl A = A : term; | |
| 90 | ||
| 91 | (*Strip and return premises: (i, [], A1==>...Ai==>B) | |
| 1460 | 92 | goes to ([Ai, A(i-1),...,A1] , B) (REVERSED) | 
| 0 | 93 | if i<0 or else i too big then raises TERM*) | 
| 94 | fun strip_prems (0, As, B) = (As, B) | |
| 95 |   | strip_prems (i, As, Const("==>", _) $ A $ B) = 
 | |
| 1460 | 96 | strip_prems (i-1, A::As, B) | 
| 0 | 97 |   | strip_prems (_, As, A) = raise TERM("strip_prems", A::As);
 | 
| 98 | ||
| 99 | (*Count premises -- quicker than (length ostrip_prems) *) | |
| 100 | fun count_prems (Const("==>", _) $ A $ B, n) = count_prems (B,n+1)
 | |
| 101 | | count_prems (_,n) = n; | |
| 102 | ||
| 103 | (** flex-flex constraints **) | |
| 104 | ||
| 64 
0bbe5d86cb38
logic/mk_equals,mk_flexpair: now calls fastype_of instead of type_of.
 lcp parents: 
0diff
changeset | 105 | (*Make a constraint.*) | 
| 
0bbe5d86cb38
logic/mk_equals,mk_flexpair: now calls fastype_of instead of type_of.
 lcp parents: 
0diff
changeset | 106 | fun mk_flexpair(t,u) = flexpair(fastype_of t) $ t $ u; | 
| 0 | 107 | |
| 108 | fun dest_flexpair (Const("=?=",_) $ t $ u)  =  (t,u)
 | |
| 109 |   | dest_flexpair t = raise TERM("dest_flexpair", [t]);
 | |
| 110 | ||
| 111 | (*make flexflex antecedents: ( [(a1,b1),...,(an,bn)] , C ) | |
| 112 | goes to (a1=?=b1) ==>...(an=?=bn)==>C *) | |
| 113 | fun list_flexpairs ([], A) = A | |
| 114 | | list_flexpairs ((t,u)::pairs, A) = | |
| 1460 | 115 | implies $ (mk_flexpair(t,u)) $ list_flexpairs(pairs,A); | 
| 0 | 116 | |
| 117 | (*Make the object-rule tpairs==>As==>B *) | |
| 118 | fun rule_of (tpairs, As, B) = list_flexpairs(tpairs, list_implies(As, B)); | |
| 119 | ||
| 120 | (*Remove and return flexflex pairs: | |
| 1460 | 121 | (a1=?=b1)==>...(an=?=bn)==>C to ( [(a1,b1),...,(an,bn)] , C ) | 
| 0 | 122 | [Tail recursive in order to return a pair of results] *) | 
| 123 | fun strip_flex_aux (pairs, Const("==>", _) $ (Const("=?=",_)$t$u) $ C) =
 | |
| 124 | strip_flex_aux ((t,u)::pairs, C) | |
| 125 | | strip_flex_aux (pairs,C) = (rev pairs, C); | |
| 126 | ||
| 127 | fun strip_flexpairs A = strip_flex_aux([], A); | |
| 128 | ||
| 129 | (*Discard flexflex pairs*) | |
| 130 | fun skip_flexpairs (Const("==>", _) $ (Const("=?=",_)$_$_) $ C) =
 | |
| 1460 | 131 | skip_flexpairs C | 
| 0 | 132 | | skip_flexpairs C = C; | 
| 133 | ||
| 134 | (*strip a proof state (Horn clause): | |
| 135 | (a1==b1)==>...(am==bm)==>B1==>...Bn==>C | |
| 136 | goes to ( [(a1,b1),...,(am,bm)] , [B1,...,Bn] , C) *) | |
| 137 | fun strip_horn A = | |
| 138 | let val (tpairs,horn) = strip_flexpairs A | |
| 139 | in (tpairs, strip_imp_prems horn, strip_imp_concl horn) end; | |
| 140 | ||
| 398 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 141 | (** types as terms **) | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 142 | |
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 143 | fun mk_type ty = Const ("TYPE", itselfT ty);
 | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 144 | |
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 145 | fun dest_type (Const ("TYPE", Type ("itself", [ty]))) = ty
 | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 146 |   | dest_type t = raise TERM ("dest_type", [t]);
 | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 147 | |
| 447 | 148 | (** class constraints **) | 
| 398 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 149 | |
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 150 | fun mk_inclass (ty, c) = | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 151 | Const (Sign.const_of_class c, itselfT ty --> propT) $ mk_type ty; | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 152 | |
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 153 | fun dest_inclass (t as Const (c_class, _) $ ty) = | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 154 | ((dest_type ty, Sign.class_of_const c_class) | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 155 |         handle TERM _ => raise TERM ("dest_inclass", [t]))
 | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 156 |   | dest_inclass t = raise TERM ("dest_inclass", [t]);
 | 
| 
41f279b477e2
added mk_type, dest_type, mk_inclass, dest_inclass (for axclasses);
 wenzelm parents: 
210diff
changeset | 157 | |
| 0 | 158 | |
| 159 | (*** Low-level term operations ***) | |
| 160 | ||
| 161 | (*Does t occur in u? Or is alpha-convertible to u? | |
| 162 | The term t must contain no loose bound variables*) | |
| 163 | fun t occs u = (t aconv u) orelse | |
| 164 | (case u of | |
| 165 | Abs(_,_,body) => t occs body | |
| 1460 | 166 | | f$t' => t occs f orelse t occs t' | 
| 167 | | _ => false); | |
| 0 | 168 | |
| 169 | (*Close up a formula over all free variables by quantification*) | |
| 170 | fun close_form A = | |
| 171 | list_all_free (map dest_Free (sort atless (term_frees A)), | |
| 1460 | 172 | A); | 
| 0 | 173 | |
| 174 | ||
| 175 | (*** Specialized operations for resolution... ***) | |
| 176 | ||
| 177 | (*For all variables in the term, increment indexnames and lift over the Us | |
| 178 | result is ?Gidx(B.(lev+n-1),...,B.lev) where lev is abstraction level *) | |
| 179 | fun incr_indexes (Us: typ list, inc:int) t = | |
| 180 | let fun incr (Var ((a,i), T), lev) = | |
| 1460 | 181 | Unify.combound (Var((a, i+inc), Us---> incr_tvar inc T), | 
| 182 | lev, length Us) | |
| 183 | | incr (Abs (a,T,body), lev) = | |
| 184 | Abs (a, incr_tvar inc T, incr(body,lev+1)) | |
| 185 | | incr (Const(a,T),_) = Const(a, incr_tvar inc T) | |
| 186 | | incr (Free(a,T),_) = Free(a, incr_tvar inc T) | |
| 187 | | incr (f$t, lev) = incr(f,lev) $ incr(t,lev) | |
| 188 | | incr (t,lev) = t | |
| 0 | 189 | in incr(t,0) end; | 
| 190 | ||
| 191 | (*Make lifting functions from subgoal and increment. | |
| 192 | lift_abs operates on tpairs (unification constraints) | |
| 193 | lift_all operates on propositions *) | |
| 194 | fun lift_fns (B,inc) = | |
| 195 |   let fun lift_abs (Us, Const("==>", _) $ _ $ B) u = lift_abs (Us,B) u
 | |
| 1460 | 196 | 	| lift_abs (Us, Const("all",_)$Abs(a,T,t)) u =
 | 
| 197 | Abs(a, T, lift_abs (T::Us, t) u) | |
| 198 | | lift_abs (Us, _) u = incr_indexes(rev Us, inc) u | |
| 0 | 199 |       fun lift_all (Us, Const("==>", _) $ A $ B) u =
 | 
| 1460 | 200 | implies $ A $ lift_all (Us,B) u | 
| 201 | 	| lift_all (Us, Const("all",_)$Abs(a,T,t)) u = 
 | |
| 202 | all T $ Abs(a, T, lift_all (T::Us,t) u) | |
| 203 | | lift_all (Us, _) u = incr_indexes(rev Us, inc) u; | |
| 0 | 204 | in (lift_abs([],B), lift_all([],B)) end; | 
| 205 | ||
| 206 | (*Strips assumptions in goal, yielding list of hypotheses. *) | |
| 207 | fun strip_assums_hyp (Const("==>", _) $ H $ B) = H :: strip_assums_hyp B
 | |
| 208 |   | strip_assums_hyp (Const("all",_)$Abs(a,T,t)) = strip_assums_hyp t
 | |
| 209 | | strip_assums_hyp B = []; | |
| 210 | ||
| 211 | (*Strips assumptions in goal, yielding conclusion. *) | |
| 212 | fun strip_assums_concl (Const("==>", _) $ H $ B) = strip_assums_concl B
 | |
| 213 |   | strip_assums_concl (Const("all",_)$Abs(a,T,t)) = strip_assums_concl t
 | |
| 214 | | strip_assums_concl B = B; | |
| 215 | ||
| 216 | (*Make a list of all the parameters in a subgoal, even if nested*) | |
| 217 | fun strip_params (Const("==>", _) $ H $ B) = strip_params B
 | |
| 218 |   | strip_params (Const("all",_)$Abs(a,T,t)) = (a,T) :: strip_params t
 | |
| 219 | | strip_params B = []; | |
| 220 | ||
| 221 | (*Removes the parameters from a subgoal and renumber bvars in hypotheses, | |
| 222 | where j is the total number of parameters (precomputed) | |
| 223 | If n>0 then deletes assumption n. *) | |
| 224 | fun remove_params j n A = | |
| 225 | if j=0 andalso n<=0 then A (*nothing left to do...*) | |
| 226 | else case A of | |
| 227 |         Const("==>", _) $ H $ B => 
 | |
| 1460 | 228 | if n=1 then (remove_params j (n-1) B) | 
| 229 | else implies $ (incr_boundvars j H) $ (remove_params j (n-1) B) | |
| 0 | 230 |       | Const("all",_)$Abs(a,T,t) => remove_params (j-1) n t
 | 
| 231 |       | _ => if n>0 then raise TERM("remove_params", [A])
 | |
| 232 | else A; | |
| 233 | ||
| 234 | (** Auto-renaming of parameters in subgoals **) | |
| 235 | ||
| 236 | val auto_rename = ref false | |
| 237 | and rename_prefix = ref "ka"; | |
| 238 | ||
| 239 | (*rename_prefix is not exported; it is set by this function.*) | |
| 240 | fun set_rename_prefix a = | |
| 241 | if a<>"" andalso forall is_letter (explode a) | |
| 242 | then (rename_prefix := a; auto_rename := true) | |
| 243 | else error"rename prefix must be nonempty and consist of letters"; | |
| 244 | ||
| 245 | (*Makes parameters in a goal have distinctive names (not guaranteed unique!) | |
| 246 | A name clash could cause the printer to rename bound vars; | |
| 247 | then res_inst_tac would not work properly.*) | |
| 248 | fun rename_vars (a, []) = [] | |
| 249 | | rename_vars (a, (_,T)::vars) = | |
| 250 | (a,T) :: rename_vars (bump_string a, vars); | |
| 251 | ||
| 252 | (*Move all parameters to the front of the subgoal, renaming them apart; | |
| 253 | if n>0 then deletes assumption n. *) | |
| 254 | fun flatten_params n A = | |
| 255 | let val params = strip_params A; | |
| 1460 | 256 | val vars = if !auto_rename | 
| 257 | then rename_vars (!rename_prefix, params) | |
| 2266 | 258 | else ListPair.zip (variantlist(map #1 params,[]), | 
| 259 | map #2 params) | |
| 0 | 260 | in list_all (vars, remove_params (length vars) n A) | 
| 261 | end; | |
| 262 | ||
| 263 | (*Makes parameters in a goal have the names supplied by the list cs.*) | |
| 264 | fun list_rename_params (cs, Const("==>", _) $ A $ B) =
 | |
| 265 | implies $ A $ list_rename_params (cs, B) | |
| 266 |   | list_rename_params (c::cs, Const("all",_)$Abs(_,T,t)) = 
 | |
| 267 | all T $ Abs(c, T, list_rename_params (cs, t)) | |
| 268 | | list_rename_params (cs, B) = B; | |
| 269 | ||
| 270 | (*Strips assumptions in goal yielding ( [Hn,...,H1], [xm,...,x1], B ) | |
| 271 | where H1,...,Hn are the hypotheses and x1...xm are the parameters. *) | |
| 272 | fun strip_assums_aux (Hs, params, Const("==>", _) $ H $ B) = 
 | |
| 1460 | 273 | strip_assums_aux (H::Hs, params, B) | 
| 0 | 274 |   | strip_assums_aux (Hs, params, Const("all",_)$Abs(a,T,t)) =
 | 
| 1460 | 275 | strip_assums_aux (Hs, (a,T)::params, t) | 
| 0 | 276 | | strip_assums_aux (Hs, params, B) = (Hs, params, B); | 
| 277 | ||
| 278 | fun strip_assums A = strip_assums_aux ([],[],A); | |
| 279 | ||
| 280 | ||
| 281 | (*Produces disagreement pairs, one for each assumption proof, in order. | |
| 282 | A is the first premise of the lifted rule, and thus has the form | |
| 283 | H1 ==> ... Hk ==> B and the pairs are (H1,B),...,(Hk,B) *) | |
| 284 | fun assum_pairs A = | |
| 285 | let val (Hs, params, B) = strip_assums A | |
| 286 | val D = Unify.rlist_abs(params, B) | |
| 287 | fun pairrev ([],pairs) = pairs | |
| 288 | | pairrev (H::Hs,pairs) = | |
| 1460 | 289 | pairrev(Hs, (Unify.rlist_abs(params,H), D) :: pairs) | 
| 0 | 290 | in pairrev (Hs,[]) (*WAS: map pair (rev Hs) *) | 
| 291 | end; | |
| 292 | ||
| 293 | ||
| 294 | (*Converts Frees to Vars and TFrees to TVars so that axioms can be written | |
| 295 | without (?) everywhere*) | |
| 296 | fun varify (Const(a,T)) = Const(a, Type.varifyT T) | |
| 297 | | varify (Free(a,T)) = Var((a,0), Type.varifyT T) | |
| 298 | | varify (Var(ixn,T)) = Var(ixn, Type.varifyT T) | |
| 299 | | varify (Abs (a,T,body)) = Abs (a, Type.varifyT T, varify body) | |
| 300 | | varify (f$t) = varify f $ varify t | |
| 301 | | varify t = t; | |
| 302 | ||
| 546 | 303 | (*Inverse of varify. Converts axioms back to their original form.*) | 
| 585 | 304 | fun unvarify (Const(a,T)) = Const(a, Type.unvarifyT T) | 
| 305 | | unvarify (Var((a,0), T)) = Free(a, Type.unvarifyT T) | |
| 306 | | unvarify (Var(ixn,T)) = Var(ixn, Type.unvarifyT T) (*non-0 index!*) | |
| 307 | | unvarify (Abs (a,T,body)) = Abs (a, Type.unvarifyT T, unvarify body) | |
| 546 | 308 | | unvarify (f$t) = unvarify f $ unvarify t | 
| 309 | | unvarify t = t; | |
| 310 | ||
| 2508 | 311 | |
| 312 | (*** term order ***) | |
| 313 | ||
| 314 | (* NB: non-linearity of the ordering is not a soundness problem *) | |
| 315 | ||
| 316 | (* FIXME: "***ABSTRACTION***" is a hack and makes the ordering non-linear *) | |
| 317 | fun string_of_hd(Const(a,_)) = a | |
| 318 | | string_of_hd(Free(a,_)) = a | |
| 319 | | string_of_hd(Var(v,_)) = Syntax.string_of_vname v | |
| 320 | | string_of_hd(Bound i) = string_of_int i | |
| 321 | | string_of_hd(Abs _) = "***ABSTRACTION***"; | |
| 322 | ||
| 323 | (* a strict (not reflexive) linear well-founded AC-compatible ordering | |
| 324 | * for terms: | |
| 325 | * s < t <=> 1. size(s) < size(t) or | |
| 326 | 2. size(s) = size(t) and s=f(...) and t = g(...) and f<g or | |
| 327 | 3. size(s) = size(t) and s=f(s1..sn) and t=f(t1..tn) and | |
| 328 | (s1..sn) < (t1..tn) (lexicographically) | |
| 329 | *) | |
| 330 | ||
| 331 | (* FIXME: should really take types into account as well. | |
| 332 | * Otherwise non-linear *) | |
| 333 | fun termord(Abs(_,_,t),Abs(_,_,u)) = termord(t,u) | |
| 334 | | termord(t,u) = | |
| 335 | (case intord(size_of_term t,size_of_term u) of | |
| 336 | EQUAL => let val (f,ts) = strip_comb t and (g,us) = strip_comb u | |
| 337 | in case stringord(string_of_hd f, string_of_hd g) of | |
| 338 | EQUAL => lextermord(ts,us) | |
| 339 | | ord => ord | |
| 340 | end | |
| 341 | | ord => ord) | |
| 342 | and lextermord(t::ts,u::us) = | |
| 343 | (case termord(t,u) of | |
| 344 | EQUAL => lextermord(ts,us) | |
| 345 | | ord => ord) | |
| 346 | | lextermord([],[]) = EQUAL | |
| 347 |   | lextermord _ = error("lextermord");
 | |
| 348 | ||
| 349 | fun termless tu = (termord tu = LESS); | |
| 350 | ||
| 0 | 351 | end; |