Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SYMBOLS_MANIFEST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ System`ChebyshevT
System`ChebyshevU
System`Check
System`CheckAbort
System`CheckArguments
System`ChessboardDistance
System`Chop
System`Circle
Expand Down
1 change: 0 additions & 1 deletion mathics/builtin/directories/directory_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ class DeleteDirectory(Builtin):
),
"nodir": "Directory `1` not found.",
"dirne": "Directory `1` not empty.",
"optx": "Unknown option `1` in `2`",
"idcts": "DeleteContents expects either True or False.", # MMA Bug
}
options = {
Expand Down
2 changes: 2 additions & 0 deletions mathics/builtin/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ class General(Builtin):
"noopen": "Cannot open `1`.",
"nord": "Invalid comparison with `1` attempted.",
"normal": "Nonatomic expression expected at position `1` in `2`.",
"notnorm": "Argument `1` must be a nonatomic expression.",
"noval": ("Symbol `1` in part assignment does not have an immediate value."),
"obspkg": "In WL, this package is obsolete.",
"openx": "`1` is not open.",
"optb": "Optional object `1` in `2` is not a single blank.",
"optnf": "Option name `1` is not a known option for `2`.",
"opttf": "Value of option `1` -> `2` should be True or False.",
"optx": "Unknown option `1` in `2`.",
"ovfl": "Overflow occurred in computation.",
"partd": "Part specification is longer than depth of object.",
"partw": "Part `1` of `2` does not exist.",
Expand Down
136 changes: 130 additions & 6 deletions mathics/builtin/options/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,142 @@
Setting Up Options for Functions
"""

from mathics.core.atoms import Integer1, String
import sys
from typing import Optional

from mathics.core.atoms import Integer, Integer1, String
from mathics.core.attributes import A_HOLD_FIRST, A_PROTECTED, A_READ_PROTECTED
from mathics.core.builtin import Builtin, get_option
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol, ensure_context
from mathics.core.systemsymbols import SymbolRule
from mathics.eval.options.function import options_to_rules
from mathics.core.rules import is_option_rule
from mathics.core.symbols import BooleanType, Symbol, ensure_context
from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolRule
from mathics.eval.options.functions import (
eval_CheckArguments,
eval_CheckArguments_with_association,
options_to_rules,
)
from mathics.eval.options.options import eval_Option_with_names, eval_Options
from mathics.eval.patterns import Matcher


class CheckArguments(Builtin):
"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/CheckArguments.html</url>

<dl>
<dt>'CheckArguments'[$f[args]$, $n$]
<dd>returns 'True" if args consists of $n$ positional arguments followed by valid options \
for $f$ and 'False' if not.

<dt>'CheckArguments'[$f[args]$, ${min, max$]
<dd>same as above but checks only the positional arguments between $min$, and $max$.
</dl>

First declare an option for function $f$:
>> Options[f] = {a -> 0}
= {a ⇾ 0}

Now check that $f$ is called with one positional argument and known options:
>> CheckArguments[f[1, a->5], 1]
= True

>> CheckArguments[f[1, 2], 1]
: f called with 2 arguments; 1 argument is expected.
= False

Allow the option named "hidden" as well as any option of 'Graphics' to be set:
>> Options[f] = {normal -> Automatic}
= {normal ⇾ Automatic}
>> CheckArguments[f[1, normal -> 3, hidden -> 2, AspectRatio -> 1], {1, 2}, <|"ExtraOptions" -> {hidden -> 0, Graphics}|>]
= True
"""

attributes = A_HOLD_FIRST | A_PROTECTED | A_READ_PROTECTED
# Set to check the number of arguments.
eval_error = Builtin.generic_argument_error
expected_args = (2, 3)
messages = {
"rspec": "The range specification `1` should have the form m, {m, n} or {m, Infinity}, "
"where m and n are integers and 0 <= m <= n."
}

summary_text = "check arguments of a function"

def eval_with_min_max(
self, expr, arg, evaluation: Evaluation
) -> Optional[BooleanType]:
"CheckArguments[expr_, arg_]"
if not isinstance(expr, Expression):
evaluation.message("CheckArguments", "notnorm", Integer1)
return

invalid_range_spec = False
if isinstance(arg, Integer):
invalid_range_spec = False
high_int = arg.value
low_int = min(1, high_int)
elif not (isinstance(arg, ListExpression) and len(arg.elements) == 2):
invalid_range_spec = True
elif not (isinstance((low := arg.elements[0]), Integer)):
invalid_range_spec = True

if invalid_range_spec:
evaluation.message("CheckArguments", "rspec", arg)
return

if isinstance(arg, ListExpression):
element1 = arg.elements[1]
low_int = arg.elements[0].value
if isinstance(element1, Integer):
high_int = element1.value
elif element1.has_form(SymbolDirectedInfinity, 1):
high_int = sys.maxsize

if not (0 <= low_int <= high_int):
evaluation.message("CheckArguments", "rspec", arg)
return

return eval_CheckArguments(expr, low_int, high_int, evaluation)

def eval_with_assoc(
self, expr, spec, assoc, evaluation: Evaluation
) -> Optional[Expression] | Symbol:
"CheckArguments[expr_, spec_, assoc_]"

if not isinstance(expr, Expression):
evaluation.message("CheckArguments", "notnorm", Integer1)
return

# Parse the spec argument
invalid_range_spec = False
if isinstance(spec, Integer):
invalid_range_spec = False
high_int = spec.value
low_int = min(1, high_int)
elif not (isinstance(spec, ListExpression) and len(spec.elements) == 2):
invalid_range_spec = True
elif not (
isinstance((low := spec.elements[0]), Integer)
and isinstance((high := spec.elements[1]), Integer)
):
invalid_range_spec = True
elif not (0 <= (low_int := low.value) <= (high_int := high.value)):
invalid_range_spec = True

if invalid_range_spec:
evaluation.message("CheckArguments", "rspec", spec)
return

return eval_CheckArguments_with_association(
expr, low_int, high_int, assoc, evaluation
)


class FilterRules(Builtin):
"""
<url>
Expand Down Expand Up @@ -45,11 +169,11 @@ class FilterRules(Builtin):
def eval(self, rules, pattern, evaluation):
"FilterRules[rules_List, pattern_]"

match = Matcher(pattern, evaluation).match
match_result = Matcher(pattern, evaluation).match

def matched():
for rule in rules.elements:
if rule.has_form("Rule", 2) and match(rule.elements[0], evaluation):
if is_option_rule(rule) and match_result(rule.elements[0], evaluation):
yield rule

return ListExpression(*list(matched()))
Expand Down
2 changes: 1 addition & 1 deletion mathics/core/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def is_option_rule(element: Any) -> bool:
or:
key :> value
"""
return element.has_form(RULE_SYMBOL_HEADS, 2)
return isinstance(element, Expression) and element.has_form(RULE_SYMBOL_HEADS, 2)


def is_rule(element: Any, include_delayed: bool = True) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion mathics/eval/options/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
mathics.builtins.options.
"""

from mathics.eval.options.function import filter_from_iterable, options_to_rules
from mathics.eval.options.functions import filter_from_iterable, options_to_rules
from mathics.eval.options.options import eval_Options
from mathics.eval.options.values import filter_non_default_values

Expand Down
26 changes: 0 additions & 26 deletions mathics/eval/options/function.py

This file was deleted.

Loading
Loading