1# -*- coding: utf-8 -*- 2 3""" 4Options and Default Arguments 5""" 6 7from mathics.version import __version__ # noqa used in loading to check consistency. 8 9from mathics.builtin.base import Builtin, Test, get_option 10from mathics.core.expression import ( 11 Symbol, 12 String, 13 Expression, 14 get_default_value, 15 ensure_context, 16 strip_context, 17) 18from mathics.builtin.drawing.image import Image 19 20 21class Options(Builtin): 22 """ 23 <dl> 24 <dt>'Options[$f$]' 25 <dd>gives a list of optional arguments to $f$ and their 26 default values. 27 </dl> 28 29 You can assign values to 'Options' to specify options. 30 >> Options[f] = {n -> 2} 31 = {n -> 2} 32 >> Options[f] 33 = {n :> 2} 34 >> f[x_, OptionsPattern[f]] := x ^ OptionValue[n] 35 >> f[x] 36 = x ^ 2 37 >> f[x, n -> 3] 38 = x ^ 3 39 40 #> f[x_, OptionsPattern[f]] := x ^ OptionValue["m"]; 41 #> Options[f] = {"m" -> 7}; 42 #> f[x] 43 = x ^ 7 44 45 Delayed option rules are evaluated just when the corresponding 'OptionValue' is called: 46 >> f[a :> Print["value"]] /. f[OptionsPattern[{}]] :> (OptionValue[a]; Print["between"]; OptionValue[a]); 47 | value 48 | between 49 | value 50 In contrast to that, normal option rules are evaluated immediately: 51 >> f[a -> Print["value"]] /. f[OptionsPattern[{}]] :> (OptionValue[a]; Print["between"]; OptionValue[a]); 52 | value 53 | between 54 55 Options must be rules or delayed rules: 56 >> Options[f] = {a} 57 : {a} is not a valid list of option rules. 58 = {a} 59 A single rule need not be given inside a list: 60 >> Options[f] = a -> b 61 = a -> b 62 >> Options[f] 63 = {a :> b} 64 Options can only be assigned to symbols: 65 >> Options[a + b] = {a -> b} 66 : Argument a + b at position 1 is expected to be a symbol. 67 = {a -> b} 68 69 #> f /: Options[f] = {a -> b} 70 = {a -> b} 71 #> Options[f] 72 = {a :> b} 73 #> f /: Options[g] := {a -> b} 74 : Rule for Options can only be attached to g. 75 = $Failed 76 77 #> Options[f] = a /; True 78 : a /; True is not a valid list of option rules. 79 = a /; True 80 """ 81 82 def apply(self, f, evaluation): 83 "Options[f_]" 84 85 name = f.get_name() 86 if not name: 87 if isinstance(f, Image): 88 # FIXME ColorSpace, MetaInformation 89 options = f.metadata 90 else: 91 evaluation.message("Options", "sym", f, 1) 92 return 93 else: 94 options = evaluation.definitions.get_options(name) 95 result = [] 96 for option, value in sorted(options.items(), key=lambda item: item[0]): 97 # Don't use HoldPattern, since the returned List should be 98 # assignable to Options again! 99 result.append(Expression("RuleDelayed", Symbol(option), value)) 100 return Expression("List", *result) 101 102 103class OptionValue(Builtin): 104 """ 105 <dl> 106 <dt>'OptionValue[$name$]' 107 <dd>gives the value of the option $name$ as specified in a 108 call to a function with 'OptionsPattern'. 109 <dt>'OptionValue[$f$, $name$]' 110 <dd>recover the value of the option $name$ associated to the symbol $f$. 111 <dt>'OptionValue[$f$, $optvals$, $name$]' 112 <dd>recover the value of the option $name$ associated to the symbol $f$, 113 extracting the values from $optvals$ if available. 114 <dt>'OptionValue[$\\ldots$, $list$]' 115 <dd>recover the value of the options in $list$ . 116 </dl> 117 118 >> f[a->3] /. f[OptionsPattern[{}]] -> {OptionValue[a]} 119 = {3} 120 121 Unavailable options generate a message: 122 >> f[a->3] /. f[OptionsPattern[{}]] -> {OptionValue[b]} 123 : Option name b not found. 124 = {b} 125 126 The argument of 'OptionValue' must be a symbol: 127 >> f[a->3] /. f[OptionsPattern[{}]] -> {OptionValue[a+b]} 128 : Argument a + b at position 1 is expected to be a symbol. 129 = {OptionValue[a + b]} 130 However, it can be evaluated dynamically: 131 >> f[a->5] /. f[OptionsPattern[{}]] -> {OptionValue[Symbol["a"]]} 132 = {5} 133 """ 134 135 messages = { 136 "optnf": "Option name `1` not found.", 137 } 138 139 rules = { 140 "OptionValue[optnames_List]": "OptionValue/@optnames", 141 "OptionValue[f_, optnames_List]": "OptionValue[f,#1]&/@optnames", 142 "OptionValue[f_, opts_, optnames_List]": "OptionValue[f,opts, #1]&/@optnames", 143 } 144 145 def apply_1(self, optname, evaluation): 146 "OptionValue[optname_]" 147 if evaluation.options is None: 148 return 149 150 if type(optname) is String: 151 name = optname.to_python()[1:-1] 152 else: 153 name = optname.get_name() 154 155 name = optname.get_name() 156 if not name: 157 name = optname.get_string_value() 158 if name: 159 name = ensure_context(name) 160 if not name: 161 evaluation.message("OptionValue", "sym", optname, 1) 162 return 163 164 val = get_option(evaluation.options, name, evaluation) 165 if val is None: 166 evaluation.message("OptionValue", "optnf", optname) 167 return Symbol(name) 168 return val 169 170 def apply_2(self, f, optname, evaluation): 171 "OptionValue[f_, optname_]" 172 return self.apply_3(f, None, optname, evaluation) 173 174 def apply_3(self, f, optvals, optname, evaluation): 175 "OptionValue[f_, optvals_, optname_]" 176 if type(optname) is String: 177 name = optname.to_python()[1:-1] 178 else: 179 name = optname.get_name() 180 181 if not name: 182 name = optname.get_string_value() 183 if name: 184 name = ensure_context(name) 185 if not name: 186 evaluation.message("OptionValue", "sym", optname, 1) 187 return 188 # Look first in the explicit list 189 if optvals: 190 val = get_option(optvals.get_option_values(evaluation), name, evaluation) 191 else: 192 val = None 193 # then, if not found, look at $f$. It could be a symbol, or a list of symbols, rules, and list of rules... 194 if val is None: 195 if f.is_symbol(): 196 val = get_option( 197 evaluation.definitions.get_options(f.get_name()), name, evaluation 198 ) 199 else: 200 if f.get_head_name() in ("System`Rule", "System`RuleDelayed"): 201 f = Expression("List", f) 202 if f.get_head_name() == "System`List": 203 for leave in f.get_leaves(): 204 if leave.is_symbol(): 205 val = get_option( 206 evaluation.definitions.get_options(leave.get_name()), 207 name, 208 evaluation, 209 ) 210 if val: 211 break 212 else: 213 values = leave.get_option_values(evaluation) 214 val = get_option(values, name, evaluation) 215 if val: 216 break 217 218 if val is None and evaluation.options: 219 val = get_option(evaluation.options, name, evaluation) 220 if val is None: 221 evaluation.message("OptionValue", "optnf", optname) 222 return Symbol(name) 223 return val 224 225 226class Default(Builtin): 227 """ 228 <dl> 229 <dt>'Default[$f$]' 230 <dd>gives the default value for an omitted paramter of $f$. 231 <dt>'Default[$f$, $k$]' 232 <dd>gives the default value for a parameter on the $k$th position. 233 <dt>'Default[$f$, $k$, $n$]' 234 <dd>gives the default value for the $k$th parameter out of $n$. 235 </dl> 236 237 Assign values to 'Default' to specify default values. 238 239 >> Default[f] = 1 240 = 1 241 >> f[x_.] := x ^ 2 242 >> f[] 243 = 1 244 245 Default values are stored in 'DefaultValues': 246 >> DefaultValues[f] 247 = {HoldPattern[Default[f]] :> 1} 248 249 You can use patterns for $k$ and $n$: 250 >> Default[h, k_, n_] := {k, n} 251 Note that the position of a parameter is relative to the pattern, not the matching expression: 252 >> h[] /. h[___, ___, x_., y_., ___] -> {x, y} 253 = {{3, 5}, {4, 5}} 254 """ 255 256 def apply(self, f, i, evaluation): 257 "Default[f_, i___]" 258 259 i = i.get_sequence() 260 if len(i) > 2: 261 evaluation.message("Default", "argb", 1 + len(i), 1, 3) 262 return 263 i = [index.get_int_value() for index in i] 264 for index in i: 265 if index is None or index < 1: 266 evaluation.message("Default", "intp") 267 return 268 name = f.get_name() 269 if not name: 270 evaluation.message("Default", "sym", f, 1) 271 return 272 result = get_default_value(name, evaluation, *i) 273 return result 274 275 276class OptionQ(Test): 277 """ 278 <dl> 279 <dt>'OptionQ[$expr$]' 280 <dd>returns 'True' if $expr$ has the form of a valid option 281 specification. 282 </dl> 283 284 Examples of option specifications: 285 >> OptionQ[a -> True] 286 = True 287 >> OptionQ[a :> True] 288 = True 289 >> OptionQ[{a -> True}] 290 = True 291 >> OptionQ[{a :> True}] 292 = True 293 294 Options lists are flattened when are applyied, so 295 >> OptionQ[{a -> True, {b->1, "c"->2}}] 296 = True 297 >> OptionQ[{a -> True, {b->1, c}}] 298 = False 299 >> OptionQ[{a -> True, F[b->1,c->2]}] 300 = False 301 302 'OptionQ' returns 'False' if its argument is not a valid option 303 specification: 304 >> OptionQ[x] 305 = False 306 """ 307 308 def test(self, expr): 309 expr = expr.flatten(Symbol("List")) 310 if not expr.has_form("List", None): 311 expr = [expr] 312 else: 313 expr = expr.get_leaves() 314 return all( 315 e.has_form("Rule", None) or e.has_form("RuleDelayed", 2) for e in expr 316 ) 317 318 319class NotOptionQ(Test): 320 """ 321 <dl> 322 <dt>'NotOptionQ[$expr$]' 323 <dd>returns 'True' if $expr$ does not have the form of a valid 324 option specification. 325 </dl> 326 327 >> NotOptionQ[x] 328 = True 329 >> NotOptionQ[2] 330 = True 331 >> NotOptionQ["abc"] 332 = True 333 334 >> NotOptionQ[a -> True] 335 = False 336 """ 337 338 def test(self, expr): 339 expr = expr.flatten(Symbol("List")) 340 if not expr.has_form("List", None): 341 expr = [expr] 342 else: 343 expr = expr.get_leaves() 344 return not all( 345 e.has_form("Rule", None) or e.has_form("RuleDelayed", 2) for e in expr 346 ) 347 348 349class FilterRules(Builtin): 350 """ 351 <dl> 352 <dt>'FilterRules[$rules$, $pattern$]' 353 <dd>gives those $rules$ that have a left side that matches $pattern$. 354 <dt>'FilterRules[$rules$, {$pattern1$, $pattern2$, ...}]' 355 <dd>gives those $rules$ that have a left side that match at least one of $pattern1$, $pattern2$, ... 356 </dl> 357 358 >> FilterRules[{x -> 100, y -> 1000}, x] 359 = {x -> 100} 360 361 >> FilterRules[{x -> 100, y -> 1000, z -> 10000}, {a, b, x, z}] 362 = {x -> 100, z -> 10000} 363 """ 364 365 rules = { 366 "FilterRules[rules_List, patterns_List]": "FilterRules[rules, Alternatives @@ patterns]", 367 } 368 369 def apply(self, rules, pattern, evaluation): 370 "FilterRules[rules_List, pattern_]" 371 from mathics.builtin.patterns import Matcher 372 373 match = Matcher(pattern).match 374 375 def matched(): 376 for rule in rules.leaves: 377 if rule.has_form("Rule", 2) and match(rule.leaves[0], evaluation): 378 yield rule 379 380 return Expression("List", *list(matched())) 381 382 383def options_to_rules(options, filter=None): 384 items = sorted(options.items()) 385 if filter: 386 items = [ 387 (name, value) 388 for name, value in items 389 if strip_context(name) in filter.keys() 390 ] 391 return [Expression("Rule", Symbol(name), value) for name, value in items] 392