diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py deleted file mode 100644 index e62dd9108..000000000 --- a/mathics/builtin/assignments/upvalues.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -""" -UpValue-related assignments - -An UpValue is a definition associated with a symbols that does not appear directly its head. - -See -:Associating Definitions with Different Symbols: -https://reference.wolfram.com/language/tutorial/TransformationRulesAndDefinitions.html#6972. -""" - -from mathics.core.assignment import get_symbol_values -from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED -from mathics.core.builtin import Builtin - - -# In Mathematica 5, this appears under "Types of Values". -class UpValues(Builtin): - """ - :WMA link: https://reference.wolfram.com/language/ref/UpValues.html -
-
'UpValues'[$symbol$] -
gives the list of transformation rules corresponding to upvalues \ - define with $symbol$. -
- - >> a + b ^= 2 - = 2 - >> UpValues[a] - = {HoldPattern[a + b] ⧴ 2} - >> UpValues[b] - = {HoldPattern[a + b] ⧴ 2} - - You can assign values to 'UpValues': - >> UpValues[pi] := {Sin[pi] :> 0} - >> Sin[pi] - = 0 - """ - - attributes = A_HOLD_ALL | A_PROTECTED - summary_text = "give a list of transformation rules corresponding to upvalues defined for a symbol" - - def eval(self, symbol, evaluation): - "UpValues[symbol_]" - - return get_symbol_values(symbol, "UpValues", "upvalues", evaluation) diff --git a/mathics/builtin/atomic/__init__.py b/mathics/builtin/atomic/__init__.py index 57cc0acd2..c1b200002 100644 --- a/mathics/builtin/atomic/__init__.py +++ b/mathics/builtin/atomic/__init__.py @@ -3,4 +3,6 @@ Atomic Elements of Expressions Expressions are ultimately built from a small number of distinct types of atomic elements. + +See also :Symbols:/doc/reference-of-built-in-symbols/symbols/. """ diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py index 424ed9b23..88e7f0a1b 100644 --- a/mathics/builtin/attributes.py +++ b/mathics/builtin/attributes.py @@ -22,20 +22,21 @@ A_LOCKED, A_PROTECTED, attribute_string_to_number, - attributes_bitset_to_list, ) from mathics.core.builtin import Builtin, Predefined from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.symbols import Symbol, SymbolNull +from mathics.core.systemsymbols import ( + SymbolClearAttributes, + SymbolProtected, + SymbolSetAttributes, +) +from mathics.eval.attributes import eval_Attributes # This tells documentation how to sort this module sort_order = "mathics.builtin.definition-attributes" -SymbolClearAttributes = Symbol("ClearAttributes") -SymbolSetAttributes = Symbol("SetAttributes") -SymbolProtected = Symbol("Protected") - class Attributes(Builtin): """ @@ -89,16 +90,7 @@ class Attributes(Builtin): def eval(self, expr, evaluation): "Attributes[expr_]" - - if isinstance(expr, String): - expr = Symbol(expr.value) - name = expr.get_lookup_name() - - attributes = attributes_bitset_to_list( - evaluation.definitions.get_attributes(name) - ) - attributes_symbols = [Symbol(attribute) for attribute in attributes] - return ListExpression(*attributes_symbols) + return eval_Attributes(expr, evaluation) class ClearAttributes(Builtin): @@ -780,12 +772,18 @@ def eval(self, symbols, evaluation): names = evaluation.definitions.get_matching_names(pattern) for defn in names: symbol = Symbol(defn) - if not A_LOCKED & evaluation.definitions.get_attributes(defn): + attributes = evaluation.definitions.get_attributes(defn) + if not A_LOCKED & attributes: items.append(symbol) - Expression( - SymbolClearAttributes, - ListExpression(*items), - protected, - ).evaluate(evaluation) + changed_list = ListExpression(*items) + if items: + Expression( + SymbolClearAttributes, + changed_list, + protected, + ).evaluate(evaluation) + + # FIXME this is wrong we should return those attributes that got changed only. + # return changed_list return SymbolNull diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index 61dcb82d5..99f84a66d 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -12,7 +12,6 @@ """ from typing import Callable, Optional -from mathics.builtin.image.base import Image from mathics.core.atoms import Integer1, String from mathics.core.builtin import Builtin, Predefined, Test, get_option from mathics.core.evaluation import Evaluation @@ -20,7 +19,8 @@ from mathics.core.list import ListExpression from mathics.core.parser import parse_builtin_rule from mathics.core.symbols import Symbol, SymbolList, ensure_context, strip_context -from mathics.core.systemsymbols import SymbolDefault, SymbolRule, SymbolRuleDelayed +from mathics.core.systemsymbols import SymbolDefault, SymbolRule +from mathics.eval.options import eval_Options from mathics.eval.patterns import Matcher, get_default_value @@ -358,23 +358,7 @@ class Options(Builtin): def eval(self, f, evaluation): "Options[f_]" - - name = f.get_name() - if not name: - if isinstance(f, Image): - # FIXME ColorSpace, MetaInformation - options = f.metadata - else: - evaluation.message("Options", "sym", f, Integer1) - return - else: - options = evaluation.definitions.get_options(name) - result = [] - for option, value in sorted(options.items(), key=lambda item: item[0]): - # Don't use HoldPattern, since the returned List should be - # assignable to Options again! - result.append(Expression(SymbolRuleDelayed, Symbol(option), value)) - return ListExpression(*result) + return eval_Options(f, evaluation) class OptionValue(Builtin): diff --git a/mathics/builtin/symbol/__init__.py b/mathics/builtin/symbol/__init__.py new file mode 100644 index 000000000..1316d7769 --- /dev/null +++ b/mathics/builtin/symbol/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +""" +Symbols + +Symbols are the atoms of symbolic data. A symbol has a unique name, exists in a Language context or namespace, \ +and can have a variety of types of values and attributes. + +Expressions are built from a small number of distinct types of atomic elements. +""" diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/symbol/properties.py similarity index 69% rename from mathics/builtin/atomic/symbols.py rename to mathics/builtin/symbol/properties.py index f541b5bbf..a6e7a621d 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/symbol/properties.py @@ -1,34 +1,26 @@ -# -*- coding: utf-8 -*- """ -Symbol Handling - -Symbolic data. Every symbol has a unique name, exists in a certain context \ -or namespace, and can have a variety of types of values and attributes. +Symbol properties """ -import re + from typing import Callable, Optional -from mathics_scanner.tokeniser import NAMES_WILDCARDS, is_symbol_name +from mathics_scanner.tokeniser import NAMES_WILDCARDS -from mathics.core.assignment import get_symbol_values -from mathics.core.atoms import Integer1, String +from mathics.core.atoms import String from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, - A_LOCKED, A_PROTECTED, A_READ_PROTECTED, - A_SEQUENCE_HOLD, attributes_bitset_to_list, ) from mathics.core.builtin import Builtin, PrefixOperator, Test from mathics.core.convert.expression import to_mathics_list -from mathics.core.convert.regex import to_regex from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.list import ListExpression -from mathics.core.rules import RewriteRule +from mathics.core.rules import RewriteRule, is_rule from mathics.core.symbols import ( Symbol, SymbolFalse, @@ -36,7 +28,6 @@ SymbolNull, SymbolTrue, SymbolUpSet, - strip_context, ) from mathics.core.systemsymbols import ( SymbolAttributes, @@ -49,14 +40,19 @@ SymbolOptions, SymbolRule, SymbolSet, + SymbolUnknownSymbol, ) from mathics.doc.online import online_doc_string from mathics.eval.atomic.symbols import eval_SymbolQ -from mathics.eval.stackframe import get_eval_Expression - -SymbolUnknownSymbol = Symbol("System`UnknownSymbol") +from mathics.eval.symbol.properties import ( + eval_Information_with_property, + eval_values, + missing_symbol, +) +# FIXME: gather_and_format_definition_rules is crap and needs to be revised, rewritten and put in +# mathics.eval.symbols.properties def gather_and_format_definition_rules( symbol: Symbol, evaluation: Evaluation ) -> Optional[list[Expression]]: @@ -175,45 +171,6 @@ def lhs_format(expr): return lines -class Context(Builtin): - r""" - :WMA link: - https://reference.wolfram.com/language/ref/Context.html -
-
'Context'[$symbol$] -
yields the name of the context where $symbol$ is defined in. - -
'Context[]' -
returns the value of '$Context'. -
- - >> Context[a] - = Global` - >> Context[b`c] - = b` - - >> InputForm[Context[]] - = "Global`" - """ - - attributes = A_HOLD_FIRST | A_PROTECTED - - rules = {"Context[]": "$Context"} - - summary_text = "give the name of the context of a symbol" - - def eval(self, symbol, evaluation): - "Context[symbol_]" - - name = symbol.get_name() - if not name: - evaluation.message("Context", "normal", Integer1, get_eval_Expression()) - return - assert "`" in name - context = name[: name.rindex("`") + 1] - return String(context) - - class Definition(Builtin): """ :WMA link: @@ -396,42 +353,32 @@ class DownValues(Builtin): attributes = A_HOLD_ALL | A_PROTECTED summary_text = "give a list of transformation rules corresponding to all downvalues defined for a symbol" - def eval(self, symbol, evaluation): - "DownValues[symbol_]" - - return get_symbol_values(symbol, "DownValues", "downvalues", evaluation) + def eval(self, name, evaluation): + "DownValues[name_]" + return eval_values(name, evaluation, "DownValues") -# In Mathematica 5, this appears under "Types of Values". -class FormatValues(Builtin): +class SymbolQ(Test): """ - :WMA link:https://reference.wolfram.com/language/tutorial/PatternsAndTransformationRules.html#6025 + :WMA link: + https://resources.wolframcloud.com/FunctionRepository/resources/SymbolQ
-
'FormatValues'[$symbol$] -
gives the list of format rules associated with $symbol$. +
'SymbolQ'[$x$] +
is 'True' if $x$ is a symbol, or 'False' otherwise.
- First, use 'Format' to set a formatting rule for a form: - - >> Format[F[x_], OutputForm]:= Subscript[x, F] - - Now, to see the rules, we can use 'FormatValues': - - >> FormatValues[F] - = {HoldPattern[Subscript[x_, F]] ⧴ Subscript[x, F]} - - The replacement pattern on the right in the delayed rule is formatted according to the top-level form. To see the rule input, we can use 'InputForm': - >> FormatValues[F] //InputForm - = {HoldPattern[Format[F[x_], OutputForm]] :> Subscript[x, F]} + >> SymbolQ[a] + = True + >> SymbolQ[1] + = False + >> SymbolQ[a + b] + = False """ - summary_text = ( - "give a list of formatting transformation rules associated with a symbol." - ) + summary_text = "test whether is a symbol" - def eval(self, symbol, evaluation): - """FormatValues[symbol_]""" - return get_symbol_values(symbol, "FormatValues", "formatvalues", evaluation) + def test(self, expr) -> bool: + return eval_SymbolQ(expr) class Information(PrefixOperator): @@ -448,12 +395,16 @@ class Information(PrefixOperator): 'Information' uses 'InputForm' to format values. """ - attributes = A_HOLD_ALL | A_SEQUENCE_HOLD | A_PROTECTED | A_READ_PROTECTED + attributes = A_PROTECTED | A_READ_PROTECTED eval_error = Builtin.generic_argument_error expected_args = (1, 2) messages = {"notfound": "Expression `1` is not a symbol"} + + # FIXME: the only valid option is ResolveContextAliases. + # LongForm is *not* an option. options = { "LongForm": "True", + "ResolveContextAliases": "True", } summary_text = "get information about all assignments for a symbol" @@ -461,8 +412,9 @@ def build_missing(self, expression: BaseElement) -> Expression: """Evaluate ?? F[x][y].. as -> Missing[UnknownSymbol, F][x][y]""" if isinstance(expression, Expression): return Expression(self.build_missing(expression.head), *expression.elements) - return Expression(SymbolMissing, SymbolUnknownSymbol, expression) + return missing_symbol(expression) + # FIXME: this is crap and needs to be moved to eval and rewritten. def build_list_of_matching_symbols( self, symbol_pat: str, evaluation: Evaluation, options: dict, grid: bool = True ): @@ -488,6 +440,16 @@ def build_list_of_matching_symbols( result = Expression(Symbol("System`TableForm"), ListExpression(*rows)) return result + def eval_with_property(self, expr, prop, evaluation: Evaluation, options: dict): + "Information[expr_, prop_, OptionsPattern[Information]]" + if is_rule(prop): + # FIXME we have an option here. + return + if not isinstance(prop, String): + return Expression(SymbolMissing, SymbolUnknownSymbol, prop) + + return eval_Information_with_property(expr, prop.value, evaluation) + # This implementation mixes the current behavior of WMA >=12.0 with the old behavior # (WMA 4.0). # TODO: the formatting part of this must be moved to `InformationData` @@ -501,8 +463,8 @@ def format_information_generic( grid: bool = True, ) -> Symbol: "(StandardForm,TraditionalForm,InputForm,OutputForm,): Information[expr_, OptionsPattern[Information]]" - evaluation.message("Information", "notfound", expr) - return self.build_missing(expr) + # expr is not a Symbol. We should leave unchanged and let other formatting rules kick in. + return None def format_information_string( self, strpat: String, evaluation: Evaluation, options: dict, grid: bool = True @@ -553,184 +515,6 @@ def format_information_symbol( return infoshow -class Names(Builtin): - """ - :WMA link: - https://reference.wolfram.com/language/ref/Names.html -
-
'Names'["$pattern$"] -
returns the list of names matching $pattern$. -
- - >> Names["List"] - = {List} - - The wildcard '*' matches any character: - >> Names["List*"] - = {List, ListLinePlot, ListLogPlot, ListPlot, ListQ, ListStepPlot, Listable} - - The wildcard '@' matches only lowercase characters: - >> Names["List@"] - = {Listable} - - >> x = 5; - >> Names["Global`*"] - = {x} - - The number of built-in symbols: - >> Length[Names["System`*"]] - = ... - """ - - summary_text = "find a list of symbols with names matching a pattern" - - def eval(self, pattern, evaluation: Evaluation): - "Names[pattern_]" - headname = pattern.get_head_name() - if headname == "System`StringExpression": - pattern = re.compile(to_regex(pattern, show_message=evaluation.message)) - else: - pattern = pattern.get_string_value() - - if pattern is None: - return - - names = set() - for full_name in evaluation.definitions.get_matching_names(pattern): - short_name = strip_context(full_name) - names.add(short_name if short_name not in names else full_name) - - # TODO: Mathematica ignores contexts when it sorts the list of - # names. - return to_mathics_list(*sorted(names), elements_conversion_fn=String) - - -# In Mathematica 5, this appears under "Types of Values". -class OwnValues(Builtin): - """ - :WMA link: - https://reference.wolfram.com/language/ref/OwnValues.html -
-
'OwnValues'[$symbol$] -
gives the list of ownvalue associated with $symbol$. -
- - >> x = 3; - >> x = 2; - >> OwnValues[x] - = {HoldPattern[x] ⧴ 2} - >> x := y - >> OwnValues[x] - = {HoldPattern[x] ⧴ y} - >> y = 5; - >> OwnValues[x] - = {HoldPattern[x] ⧴ y} - >> Hold[x] /. OwnValues[x] - = Hold[y] - >> Hold[x] /. OwnValues[x] // ReleaseHold - = 5 - """ - - attributes = A_HOLD_ALL | A_PROTECTED - summary_text = "give the rule corresponding to any ownvalue defined for a symbol" - - def eval(self, symbol, evaluation): - "OwnValues[symbol_]" - - return get_symbol_values(symbol, "OwnValues", "ownvalues", evaluation) - - -class Symbol_(Builtin): - """ - :WMA link: - https://reference.wolfram.com/language/ref/Symbol.html -
-
'Symbol' -
is the head of symbols. -
- - >> Head[x] - = Symbol - You can use 'Symbol' to create symbols from strings: - >> Symbol["x"] + Symbol["x"] - = 2 x - """ - - attributes = A_LOCKED | A_PROTECTED - eval_error = Builtin.generic_argument_error - expected_args = 1 - - messages = { - "symname": ( - "The string `1` cannot be used for a symbol name. " - "A symbol name must start with a letter " - "followed by letters and numbers." - ), - } - - name = "Symbol" - - summary_text = "the head of a symbol; create a symbol from a name" - - def eval(self, string, evaluation): - "Symbol[string_String]" - - text = string.value - if is_symbol_name(text): - return Symbol(evaluation.definitions.lookup_name(string.value)) - else: - evaluation.message("Symbol", "symname", string) - - -class SymbolName(Builtin): - """ - :WMA link: - https://reference.wolfram.com/language/ref/SymbolName.html -
-
'SymbolName'[$s$] -
returns the name of the symbol $s$ (without any leading \ - context name). -
- - >> SymbolName[x] // InputForm - = "x" - """ - - eval_error = Builtin.generic_argument_error - expected_args = 1 - summary_text = "give the name of a symbol as a string" - - def eval(self, symbol, evaluation): - "SymbolName[symbol_Symbol]" - - # MMA docs say "SymbolName always give the short name, - # without any context" - return String(strip_context(symbol.get_name())) - - -class SymbolQ(Test): - """ - :WMA link: - https://resources.wolframcloud.com/FunctionRepository/resources/SymbolQ -
-
'SymbolQ'[$x$] -
is 'True' if $x$ is a symbol, or 'False' otherwise. -
- - >> SymbolQ[a] - = True - >> SymbolQ[1] - = False - >> SymbolQ[a + b] - = False - """ - - summary_text = "test whether is a symbol" - - def test(self, expr) -> bool: - return eval_SymbolQ(expr) - - class ValueQ(Builtin): """ :WMA link: @@ -758,3 +542,34 @@ def eval(self, expr, evaluation): if expr.sameQ(evaluated_expr): return SymbolFalse return SymbolTrue + + +# In Mathematica 5, this appears under "Types of Values". +class UpValues(Builtin): + """ + :WMA link: https://reference.wolfram.com/language/ref/UpValues.html +
+
'UpValues'[$symbol$] +
gives the list of transformation rules corresponding to upvalues \ + define with $symbol$. +
+ + >> a + b ^= 2 + = 2 + >> UpValues[a] + = {HoldPattern[a + b] ⧴ 2} + >> UpValues[b] + = {HoldPattern[a + b] ⧴ 2} + + You can assign values to 'UpValues': + >> UpValues[pi] := {Sin[pi] :> 0} + >> Sin[pi] + = 0 + """ + + attributes = A_HOLD_ALL | A_PROTECTED + summary_text = "give a list of transformation rules corresponding to upvalues defined for a symbol" + + def eval(self, name, evaluation): + "UpValues[name_]" + return eval_values(name, evaluation, "UpValues") diff --git a/mathics/builtin/symbol/symbols.py b/mathics/builtin/symbol/symbols.py new file mode 100644 index 000000000..24e561c7b --- /dev/null +++ b/mathics/builtin/symbol/symbols.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +""" +Basic Symbol components +""" +import re + +from mathics_scanner.tokeniser import is_symbol_name + +from mathics.core.assignment import get_symbol_values +from mathics.core.atoms import Integer1, String +from mathics.core.attributes import A_HOLD_ALL, A_HOLD_FIRST, A_LOCKED, A_PROTECTED +from mathics.core.builtin import Builtin +from mathics.core.convert.expression import to_mathics_list +from mathics.core.convert.regex import to_regex +from mathics.core.evaluation import Evaluation +from mathics.core.symbols import Symbol, strip_context +from mathics.eval.stackframe import get_eval_Expression + + +class Context(Builtin): + r""" + :WMA link: + https://reference.wolfram.com/language/ref/Context.html +
+
'Context'[$symbol$] +
yields the name of the context where $symbol$ is defined in. + +
'Context[]' +
returns the value of '$Context'. +
+ + >> Context[a] + = Global` + >> Context[b`c] + = b` + + >> InputForm[Context[]] + = "Global`" + """ + + attributes = A_HOLD_FIRST | A_PROTECTED + + rules = {"Context[]": "$Context"} + + summary_text = "give the name of the context of a symbol" + + def eval(self, symbol, evaluation): + "Context[symbol_]" + + name = symbol.get_name() + if not name: + evaluation.message("Context", "normal", Integer1, get_eval_Expression()) + return + assert "`" in name + context = name[: name.rindex("`") + 1] + return String(context) + + +# In Mathematica 5, this appears under "Types of Values". +class FormatValues(Builtin): + """ + :WMA link:https://reference.wolfram.com/language/tutorial/PatternsAndTransformationRules.html#6025 +
+
'FormatValues'[$symbol$] +
gives the list of format rules associated with $symbol$. +
+ + First, use 'Format' to set a formatting rule for a form: + + >> Format[F[x_], OutputForm]:= Subscript[x, F] + + Now, to see the rules, we can use 'FormatValues': + + >> FormatValues[F] + = {HoldPattern[Subscript[x_, F]] ⧴ Subscript[x, F]} + + The replacement pattern on the right in the delayed rule is formatted according to the top-level form. To see the rule input, we can use 'InputForm': + >> FormatValues[F] //InputForm + = {HoldPattern[Format[F[x_], OutputForm]] :> Subscript[x, F]} + """ + + summary_text = ( + "give a list of formatting transformation rules associated with a symbol." + ) + + def eval(self, symbol, evaluation): + """FormatValues[symbol_]""" + return get_symbol_values(symbol, "FormatValues", "formatvalues", evaluation) + + +class Names(Builtin): + """ + :WMA link: + https://reference.wolfram.com/language/ref/Names.html +
+
'Names'["$pattern$"] +
returns the list of names matching $pattern$. +
+ + >> Names["List"] + = {List} + + The wildcard '*' matches any character: + >> Names["List*"] + = {List, ListLinePlot, ListLogPlot, ListPlot, ListQ, ListStepPlot, Listable} + + The wildcard '@' matches only lowercase characters: + >> Names["List@"] + = {Listable} + + >> x = 5; + >> Names["Global`*"] + = {x} + + The number of built-in symbols: + >> Length[Names["System`*"]] + = ... + """ + + summary_text = "find a list of symbols with names matching a pattern" + + def eval(self, pattern, evaluation: Evaluation): + "Names[pattern_]" + headname = pattern.get_head_name() + if headname == "System`StringExpression": + pattern = re.compile(to_regex(pattern, show_message=evaluation.message)) + else: + pattern = pattern.get_string_value() + + if pattern is None: + return + + names = set() + for full_name in evaluation.definitions.get_matching_names(pattern): + short_name = strip_context(full_name) + names.add(short_name if short_name not in names else full_name) + + # TODO: Mathematica ignores contexts when it sorts the list of + # names. + return to_mathics_list(*sorted(names), elements_conversion_fn=String) + + +# In Mathematica 5, this appears under "Types of Values". +class OwnValues(Builtin): + """ + :WMA link: + https://reference.wolfram.com/language/ref/OwnValues.html +
+
'OwnValues'[$symbol$] +
gives the list of ownvalue associated with $symbol$. +
+ + >> x = 3; + >> x = 2; + >> OwnValues[x] + = {HoldPattern[x] ⧴ 2} + >> x := y + >> OwnValues[x] + = {HoldPattern[x] ⧴ y} + >> y = 5; + >> OwnValues[x] + = {HoldPattern[x] ⧴ y} + >> Hold[x] /. OwnValues[x] + = Hold[y] + >> Hold[x] /. OwnValues[x] // ReleaseHold + = 5 + """ + + attributes = A_HOLD_ALL | A_PROTECTED + summary_text = "give the rule corresponding to any ownvalue defined for a symbol" + + def eval(self, symbol, evaluation): + "OwnValues[symbol_]" + + return get_symbol_values(symbol, "OwnValues", "ownvalues", evaluation) + + +class Symbol_(Builtin): + """ + :WMA link: + https://reference.wolfram.com/language/ref/Symbol.html +
+
'Symbol' +
is the head of symbols. +
+ + >> Head[x] + = Symbol + You can use 'Symbol' to create symbols from strings: + >> Symbol["x"] + Symbol["x"] + = 2 x + """ + + attributes = A_LOCKED | A_PROTECTED + eval_error = Builtin.generic_argument_error + expected_args = 1 + + messages = { + "symname": ( + "The string `1` cannot be used for a symbol name. " + "A symbol name must start with a letter " + "followed by letters and numbers." + ), + } + + name = "Symbol" + + summary_text = "the head of a symbol; create a symbol from a name" + + def eval(self, string, evaluation): + "Symbol[string_String]" + + text = string.value + if is_symbol_name(text): + return Symbol(evaluation.definitions.lookup_name(string.value)) + else: + evaluation.message("Symbol", "symname", string) + + +class SymbolName(Builtin): + """ + :WMA link: + https://reference.wolfram.com/language/ref/SymbolName.html +
+
'SymbolName'[$s$] +
returns the name of the symbol $s$ (without any leading \ + context name). +
+ + >> SymbolName[x] // InputForm + = "x" + """ + + eval_error = Builtin.generic_argument_error + expected_args = 1 + summary_text = "give the name of a symbol as a string" + + def eval(self, symbol, evaluation): + "SymbolName[symbol_Symbol]" + + # MMA docs say "SymbolName always give the short name, + # without any context" + return String(strip_context(symbol.get_name())) diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py index a75fcd28a..6dad5e0bc 100644 --- a/mathics/core/assignment.py +++ b/mathics/core/assignment.py @@ -7,6 +7,7 @@ from typing import Callable, List, Optional, Tuple +from mathics.core.atoms import Integer1 from mathics.core.attributes import A_PROTECTED from mathics.core.builtin import Builtin from mathics.core.definitions import Definitions @@ -102,7 +103,7 @@ def get_symbol_values( """ if not isinstance(symbol, Symbol): - evaluation.message(func_name, "sym", symbol, 1) + evaluation.message(func_name, "sym", symbol, Integer1) return None name = symbol.get_name() definitions = evaluation.definitions diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 4e936bb97..95f0934a0 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -68,6 +68,7 @@ SymbolCases = Symbol("System`Cases") SymbolCatalan = Symbol("System`Catalan") SymbolCeiling = Symbol("System`Ceiling") +SymbolClearAttributes = Symbol("System`ClearAttributes") SymbolClipPlanes = Symbol("System`ClipPlanes") SymbolClipPlanesStyle = Symbol("System`ClipPlanesStyle") SymbolClusteringComponents = Symbol("System`ClusteringComponents") @@ -145,6 +146,7 @@ SymbolFunction = Symbol("System`Function") SymbolGamma = Symbol("System`Gamma") SymbolGet = Symbol("System`Get") +SymbolGlaisher = Symbol("System`Glaisher") SymbolGoldenRatio = Symbol("System`GoldenRatio") SymbolGraphics = Symbol("System`Graphics") SymbolGraphics3D = Symbol("System`Graphics3D") @@ -283,6 +285,7 @@ SymbolPrefix = Symbol("System`Prefix") SymbolPreserveImageOptions = Symbol("System`PreserveImageOptions") SymbolProlog = Symbol("System`Prolog") +SymbolProtected = Symbol("System`Protected") SymbolQuantity = Symbol("System`Quantity") SymbolQuiet = Symbol("System`Quiet") SymbolQuotient = Symbol("System`Quotient") @@ -318,6 +321,7 @@ SymbolSeries = Symbol("System`Series") SymbolSeriesData = Symbol("System`SeriesData") SymbolSet = Symbol("System`Set") +SymbolSetAttributes = Symbol("System`SetAttributes") SymbolSign = Symbol("System`Sign") SymbolSimplify = Symbol("System`Simplify") SymbolSin = Symbol("System`Sin") @@ -377,6 +381,8 @@ SymbolUndefined = Symbol("System`Undefined") SymbolUnequal = Symbol("System`Unequal") SymbolUnevaluated = Symbol("System`Unevaluated") +SymbolUnknownProperty = Symbol("System`UnknownProperty") +SymbolUnknownSymbol = Symbol("System`UnknownSymbol") SymbolUpValues = Symbol("System`UpValues") SymbolVariance = Symbol("System`Variance") SymbolVerbatim = Symbol("Verbatim") diff --git a/mathics/eval/attributes.py b/mathics/eval/attributes.py new file mode 100644 index 000000000..fe0aa7446 --- /dev/null +++ b/mathics/eval/attributes.py @@ -0,0 +1,19 @@ +""" +Evaluation functions for builtins in mathics.core.builtin.attributes +""" + +from mathics.core.atoms import String +from mathics.core.attributes import attributes_bitset_to_list +from mathics.core.evaluation import Evaluation +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol + + +def eval_Attributes(expr, evaluation: Evaluation): + if isinstance(expr, String): + expr = Symbol(expr.value) + name = expr.get_lookup_name() + + attributes = attributes_bitset_to_list(evaluation.definitions.get_attributes(name)) + attributes_symbols = [Symbol(attribute) for attribute in attributes] + return ListExpression(*attributes_symbols) diff --git a/mathics/eval/options.py b/mathics/eval/options.py new file mode 100644 index 000000000..2ba895745 --- /dev/null +++ b/mathics/eval/options.py @@ -0,0 +1,28 @@ +from typing import Optional + +from mathics.builtin.image.base import Image +from mathics.core.atoms import Integer1 +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import SymbolRuleDelayed + + +def eval_Options(f, evaluation: Evaluation) -> Optional[ListExpression]: + name = f.get_name() + if not name: + if isinstance(f, Image): + # FIXME ColorSpace, MetaInformation + options = f.metadata + else: + evaluation.message("Options", "sym", f, Integer1) + return + else: + options = evaluation.definitions.get_options(name) + result = [] + for option, value in sorted(options.items(), key=lambda item: item[0]): + # Don't use HoldPattern, since the returned List should be + # assignable to Options again! + result.append(Expression(SymbolRuleDelayed, Symbol(option), value)) + return ListExpression(*result) diff --git a/mathics/eval/symbol/__init__.py b/mathics/eval/symbol/__init__.py new file mode 100644 index 000000000..1932a0a62 --- /dev/null +++ b/mathics/eval/symbol/__init__.py @@ -0,0 +1,4 @@ +""" +Evaluation routines and associated code for Built-in functions found under module +mathics.builtins.symbol.properties. +""" diff --git a/mathics/eval/symbol/properties.py b/mathics/eval/symbol/properties.py new file mode 100644 index 000000000..401027ebe --- /dev/null +++ b/mathics/eval/symbol/properties.py @@ -0,0 +1,326 @@ +""" +Evaluation routines for builtin function in mathics.core.builtin.symbol.properties. +""" + +from functools import cache +from typing import Final + +from mathics.core.assignment import get_symbol_values +from mathics.core.atoms import String +from mathics.core.attributes import A_READ_PROTECTED +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( + SymbolMissing, + SymbolNone, + SymbolUnknownProperty, + SymbolUnknownSymbol, +) +from mathics.eval.attributes import eval_Attributes +from mathics.eval.options import eval_Options + +ALL_PROPERTIES: Final[ListExpression] = ListExpression( + String("Attributes"), + String("DefaultValues"), + # String("Definitions"), + String("DownValues"), + String("FormatValues"), + String("FullName"), + String("NValues"), + String("Options"), + String("Ownvalues"), + String("SubValues"), + String("UpValues"), + # String("Usage"), +) + + +def eval_Information_with_property(expr, property: str, evaluation: Evaluation): + match property: + case "Attributes": + return eval_Attributes(expr, evaluation) + case ( + "DefaultValues" + | "DownValues" + | "FormatValues" + | "NValues" + | "OwnValues" + | "SubValues" + | "UpValues" + ): + return information_values(expr, evaluation, property) + # case "Definitions": + # return information_values(expr, evaluation.definitions, "defaultvalues") + return information_fullname(expr) + case "Options": + return eval_Options(expr, evaluation) + case "Properties": + return ALL_PROPERTIES + # case "Usage": + # return information_values(expr, evaluation.definitions, "upvalues") + case _: + return missing_property(property) + return + + +def eval_values(name: Symbol | String, evaluation: Evaluation, attribute: str): + """ + Evaluation function for xxxValues, e.g. DownValues, UpValues, ... + """ + name_symbol = ( + Symbol(evaluation.definitions.lookup_name(name.value)) + if isinstance(name, String) + else name + ) + return get_symbol_values(name_symbol, attribute, attribute.lower(), evaluation) + + +def information_values(name: Symbol | String, evaluation, value_type: str): + """ + Evaluation function for Information[name, xxxValues]. + This is similar to eval_values, however the results change slightly + 1. 'None' returned in the key is not found + 2. The READ_PROTECTED attribute is is respected + 3. Empty values return 'None' instead of {}. + + """ + if isinstance(name, String): + name_str = name.value + name_symbol = Symbol(evaluation.definitions.lookup_name(name.value)) + else: + name_str = name.name + name_symbol = name + + definitions = evaluation.definitions + name = definitions.lookup_name(name_str) + try: + definition = definitions.get_definition(name_str, only_if_exists=True) + except KeyError: + return SymbolNone + + attributes = definition.attributes + if not A_READ_PROTECTED & attributes: + return SymbolNone + values = get_symbol_values(name_symbol, value_type, value_type.lower(), evaluation) + return SymbolNone if not values else values + + +# FIXME there should be an eval_Fullname for this. +def information_fullname(symbol: Symbol): + return String(symbol) + + +@cache +def missing_property(property) -> Expression: + return Expression(SymbolMissing, SymbolUnknownProperty, Symbol(property)) + + +@cache +def missing_symbol(expression) -> Expression: + return Expression(SymbolMissing, SymbolUnknownSymbol, expression) + + +# The stuff below is from old Mathics code and needs to be revised +# and rewritten. + +# def gather_and_format_definition_rules( +# symbol: Symbol, evaluation: Evaluation +# ) -> Optional[list[Expression]]: +# """Return a list of lines describing the definition of `symbol`""" +# lines = [] + +# def rhs_format(expr): +# if expr.has_form("Infix", None): +# expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) +# return expr + +# def format_rule( +# rule: RewriteRule, +# up: bool = False, +# lhs: Callable = lambda k: k, +# rhs: Callable = lambda r: r, +# ): +# """ +# Add a line showing `rule` +# """ +# evaluation.check_stopped() +# if isinstance(rule, RewriteRule): +# lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) +# repl_expr = rhs( +# rule.replace.replace_vars( +# {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} +# ) +# ) +# repl_expr = Expression(SymbolInputForm, repl_expr) +# lines.append( +# Expression( +# SymbolHoldForm, +# Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), +# ) +# ) + +# def gather_rules(definition: Definition): +# """ +# Add to the description all the rules associated +# to a definition object +# """ +# for rule in definition.ownvalues: +# format_rule(rule) +# for rule in definition.downvalues: +# format_rule(rule) +# for rule in definition.subvalues: +# format_rule(rule) +# for rule in definition.upvalues: +# format_rule(rule, up=True) +# for rule in definition.nvalues: +# format_rule(rule) +# formats = sorted(definition.formatvalues.items()) +# for form_name, rules in formats: +# for rule in rules: + +# def lhs_format(expr): +# return Expression(SymbolFormat, expr, Symbol(form_name)) + +# format_rule(rule, lhs=lhs_format, rhs=rhs_format) + +# name = symbol.get_name() +# if not name: +# evaluation.message("Definition", "sym", symbol, 1) +# return + +# try: +# all = evaluation.definitions.get_definition(name) +# attributes = all.attributes +# all_options = all.options +# all_defaultvalues = all.defaultvalues + +# if attributes: +# attributes_list = attributes_bitset_to_list(attributes) +# lines.append( +# Expression( +# SymbolHoldForm, +# Expression( +# SymbolSet, +# Expression(SymbolAttributes, symbol), +# to_mathics_list( +# *attributes_list, elements_conversion_fn=Symbol +# ), +# ), +# ) +# ) +# except KeyError: +# attributes = 0 +# all_options = {} +# all_defaultvalues = [] + +# if not A_READ_PROTECTED & attributes: +# try: +# gather_rules(evaluation.definitions.get_user_definition(name, create=False)) +# except KeyError: +# pass + +# for rule in all_defaultvalues: +# format_rule(rule) +# if all_options: +# options = sorted(all_options.items()) +# lines.append( +# Expression( +# SymbolHoldForm, +# Expression( +# SymbolSet, +# Expression(SymbolOptions, symbol), +# ListExpression( +# *( +# Expression(SymbolRule, Symbol(name), value) +# for name, value in options +# ) +# ), +# ), +# ) +# ) +# return lines + + +# def build_list_of_matching_symbols( +# self, symbol_pat: str, evaluation: Evaluation, options: dict, grid: bool = True +# ): +# """Return a list of symbols compatible with symbol_pat""" +# definitions = evaluation.definitions +# names = definitions.get_matching_names(symbol_pat) +# if len(names) == 1: +# return self.format_information_symbol( +# Symbol(definitions.lookup_name(names[0])), evaluation, options +# ) +# rows = [] +# curr_row = [] +# for name in names: +# curr_row.append(String(definitions.shorten_name(name))) +# if len(curr_row) == 3: +# rows.append(ListExpression(*curr_row)) +# curr_row = [] +# if curr_row: +# curr_row = curr_row + (3 - len(curr_row)) * [String("")] +# rows.append(ListExpression(*curr_row)) + +# # Build Association with Information fields for each matching symbol +# associations = [] +# for name in names: +# symbol = Symbol(definitions.lookup_name(name)) + +# # Get symbol definition to extract attributes and values +# try: +# definition = definitions.get_definition(name) +# except KeyError: +# definition = None + +# # Extract components for Association +# assoc_items = [ +# Expression(SymbolRule, String("FullName"), String(name)), +# ] + +# # Add Attributes +# if definition and definition.attributes: +# attributes_list = attributes_bitset_to_list(definition.attributes) +# assoc_items.append( +# Expression( +# SymbolRule, +# String("Attributes"), +# to_mathics_list( +# *attributes_list, elements_conversion_fn=Symbol +# ), +# ) +# ) + +# # Add Definitions (downvalues, ownvalues, upvalues) +# definition_rules = gather_and_format_definition_rules(symbol, evaluation) +# if definition_rules: +# assoc_items.append( +# Expression( +# SymbolRule, +# String("Definitions"), +# ListExpression(*definition_rules), +# ) +# ) + +# # Add OwnValues +# ownvalues = get_symbol_values(symbol, "OwnValues", "ownvalues", evaluation) +# if ownvalues: +# assoc_items.append( +# Expression(SymbolRule, String("OwnValues"), ownvalues) +# ) + +# # Add DownValues +# downvalues = get_symbol_values( +# symbol, "DownValues", "downvalues", evaluation +# ) +# if downvalues: +# assoc_items.append( +# Expression(SymbolRule, String("DownValues"), downvalues) +# ) + +# associations.append(Expression(SymbolAssociation, *assoc_items)) + +# result = ListExpression(*associations) +# return result diff --git a/test/builtin/assignments/test_assignment.py b/test/builtin/assignments/test_assignment.py index 288a378c0..a6f1243fa 100644 --- a/test/builtin/assignments/test_assignment.py +++ b/test/builtin/assignments/test_assignment.py @@ -435,35 +435,6 @@ def test_process_assign_other(): ) -@pytest.mark.parametrize( - ("str_expr", "str_expected", "msgs", "failure_msg"), - [ - (None, None, None, None), - # From Clear - ("x = 2;OwnValues[x]=.;x", "x", None, "Erase Ownvalues"), - ("f[a][b] = 3; SubValues[f] =.;f[a][b]", "f[a][b]", None, "Erase Subvalues"), - ("PrimeQ[p] ^= True; PrimeQ[p]", "True", None, "Subvalues"), - ("UpValues[p]=.; PrimeQ[p]", "False", None, "Erase Subvalues"), - ("a + b ^= 5; a =.; a + b", "5", None, None), - ("{UpValues[a], UpValues[b]} =.; a+b", "a+b", None, None), - ( - "Unset[Messages[1]]", - "$Failed", - [ - "First argument in Messages[1] is not a symbol or a string naming a symbol." - ], - "Unset Message", - ), - (" g[a+b] ^:= 2", "$Failed", ("Tag Plus in g[a + b] is Protected.",), None), - (" g[a+b]", "g[a + b]", None, None), - ], -) -def test_upvalues_ownvalues(str_expr, str_expected, msgs, failure_msg): - check_evaluation( - str_expr, str_expected, expected_messages=msgs, failure_message=failure_msg - ) - - @pytest.mark.parametrize( ["expr", "expect", "fail_msg", "expected_msgs"], [ diff --git a/test/builtin/symbol/__init__.py b/test/builtin/symbol/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/test/builtin/symbol/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/test/builtin/symbol/test_properties.py b/test/builtin/symbol/test_properties.py new file mode 100644 index 000000000..9de1ff186 --- /dev/null +++ b/test/builtin/symbol/test_properties.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +""" +Unit tests from mathics.builtin.symbol.properties +""" +from test.helper import check_evaluation + +import pytest + + +def test_downvalues(): + for str_expr, str_expected, assert_fail_message in ( + ( + "DownValues[foo]={x_^2:>y}", + "{x_ ^ 2 :> y}", + "Issue #1251 part 1", + ), + ( + "DownValues[foo]", + "{HoldPattern[x_ ^ 2] :> y}", + "Check that we get the DownValue just assigned.", + ), + ( + 'DownValues["foo"]', + "{HoldPattern[x_ ^ 2] :> y}", + "A string is allowed in as a DownValues argument.", + ), + ( + "PrependTo[DownValues[foo], {x_^3:>z}]", + "{{x_ ^ 3 :> z}, HoldPattern[x_ ^ 2] :> y}", + "Issue #1251 part 2", + ), + ( + "DownValues[foo]={x_^3:>y}", + "{x_ ^ 3 :> y}", + "Issue #1251 part 3", + ), + ): + check_evaluation( + str_expr, + str_expected, + assert_fail_message, + to_string_expr=False, + to_string_expected=False, + ) + + +@pytest.mark.parametrize( + ("str_expr", "str_expected", "msgs", "failure_msg"), + [ + (None, None, None, None), + # From Clear + ("x = 2;OwnValues[x]=.;x", "x", None, "Erase Ownvalues"), + ("f[a][b] = 3; SubValues[f] =.;f[a][b]", "f[a][b]", None, "Erase Subvalues"), + ("PrimeQ[p] ^= True; PrimeQ[p]", "True", None, "Subvalues"), + ("UpValues[p]=.; PrimeQ[p]", "False", None, "Erase Subvalues"), + ("a + b ^= 5; a =.; a + b", "5", None, None), + ("{UpValues[a], UpValues[b]} =.; a+b", "a+b", None, None), + ( + "Unset[Messages[1]]", + "$Failed", + [ + "First argument in Messages[1] is not a symbol or a string naming a symbol." + ], + "Unset Message", + ), + (" g[a+b] ^:= 2", "$Failed", ("Tag Plus in g[a + b] is Protected.",), None), + (" g[a+b]", "g[a + b]", None, None), + ], +) +def test_upvalues_ownvalues(str_expr, str_expected, msgs, failure_msg): + check_evaluation( + str_expr, str_expected, expected_messages=msgs, failure_message=failure_msg + ) diff --git a/test/builtin/atomic/test_symbols.py b/test/builtin/symbol/test_symbols.py similarity index 79% rename from test/builtin/atomic/test_symbols.py rename to test/builtin/symbol/test_symbols.py index 6c9180289..46c4e8038 100644 --- a/test/builtin/atomic/test_symbols.py +++ b/test/builtin/symbol/test_symbols.py @@ -1,33 +1,12 @@ # -*- coding: utf-8 -*- """ -Unit tests from mathics.builtin.atomic.symbols. +Unit tests from mathics.builtin.symbol.symbols. """ from test.helper import check_arg_counts, check_evaluation import pytest -def test_downvalues(): - for str_expr, str_expected, message in ( - ( - "DownValues[foo]={x_^2:>y}", - "{x_ ^ 2 :> y}", - "Issue #1251 part 1", - ), - ( - "PrependTo[DownValues[foo], {x_^3:>z}]", - "{{x_ ^ 3 :> z}, HoldPattern[x_ ^ 2] :> y}", - "Issue #1251 part 2", - ), - ( - "DownValues[foo]={x_^3:>y}", - "{x_ ^ 3 :> y}", - "Issue #1251 part 3", - ), - ): - check_evaluation(str_expr, str_expected, message) - - @pytest.mark.parametrize( ("str_expr", "warnings", "str_expected", "fail_msg"), [ @@ -39,8 +18,19 @@ def test_downvalues(): ("a`x === b`x", None, "False", None), ## awkward parser cases ("FullForm[a`b_]", None, "Pattern[a`b, Blank[]]", None), - ("a = 2;", None, "Null", None), - ("Information[a]", tuple(), "Global`a\n\na = 2\n", None), + ( + "Clear[a]; Information[a]", + tuple(), + "Global`a\n", + "When a symbol is not bound, Information[] of that symbol should return the context and name.", + ), + ( + "a = 2;", + None, + "Null", + "When a symbol is bound, Information[] of that symbol do anything.", + ), + ("Information[a]", tuple(), "Information[2]", None), ( "{?? q, ?? q}", tuple(), diff --git a/test/builtin/symbolic_history/test_stack.py b/test/builtin/symbolic_history/test_stack.py index 8966d9d1e..aeeb23646 100644 --- a/test/builtin/symbolic_history/test_stack.py +++ b/test/builtin/symbolic_history/test_stack.py @@ -13,7 +13,7 @@ def test_trace(): "Trace with Constant - no intermediate values", ), ( - "Trace[a]", + "Clear[a]; Trace[a]", "{}", "Trace with Symbol - no intermediate values", ),