diff --git a/docs/extending/developing-code/extending/case-studies/selecting-builtin.rst b/docs/extending/developing-code/extending/case-studies/selecting-builtin.rst index 1f479db..27f9d6a 100644 --- a/docs/extending/developing-code/extending/case-studies/selecting-builtin.rst +++ b/docs/extending/developing-code/extending/case-studies/selecting-builtin.rst @@ -30,12 +30,9 @@ In a cloned git directory, you can find some of missing using ``git grep -n '^# Below, we have a few examples of some actual Mathics3 Builtins that we have added. Hopefully you can use this as an aid for filling in one of the many Builtin Functions, such as one of the above. -.. toctree:: - :maxdepth: 1 - - Undefined/index - KroneckerProduct/index - Curl/index +* :ref: `Undefined` +* :ref: `KroneckerProduct` +* :ref: `Curl` As we added these Builtins, we recorded the steps that were taken. We ordered the list above to go from the simple to more advanced. diff --git a/docs/extending/developing-code/extending/tutorial.rst b/docs/extending/developing-code/extending/tutorial.rst index 2686bfe..a1ea00a 100644 --- a/docs/extending/developing-code/extending/tutorial.rst +++ b/docs/extending/developing-code/extending/tutorial.rst @@ -13,12 +13,13 @@ After reading this you may want to go through the detailed examples in :ref:`Cas tutorial/0-predefined tutorial/1-builtin - tutorial/2-help-markup - tutorial/3-test-markup - tutorial/4-patterns - tutorial/5-rules - tutorial/6-attributes - tutorial/7-warnings + tutorial/2-parameter-checking + tutorial/3-help-markup + tutorial/4-test-markup + tutorial/5-patterns + tutorial/6-rules + tutorial/7-attributes + tutorial/8-warnings .. TODO: Document Operator and SympyFunction .. TODO: Document interrupts diff --git a/docs/extending/developing-code/extending/tutorial/1-builtin.rst b/docs/extending/developing-code/extending/tutorial/1-builtin.rst index ede0803..a95ed2e 100644 --- a/docs/extending/developing-code/extending/tutorial/1-builtin.rst +++ b/docs/extending/developing-code/extending/tutorial/1-builtin.rst @@ -62,7 +62,7 @@ The docstring for the ``eval`` method is a Mathics3 pattern: "Hello[person_String]" -Inside Mathic3 you can test what this matches using the Mathics3 ``MatchQ[]`` function: +Inside Mathics3, you can test what this matches using the Mathics3 ``MatchQ[]`` function: .. code-block:: mathematica diff --git a/docs/extending/developing-code/extending/tutorial/2-parameter-checking.rst b/docs/extending/developing-code/extending/tutorial/2-parameter-checking.rst new file mode 100644 index 0000000..e57fa27 --- /dev/null +++ b/docs/extending/developing-code/extending/tutorial/2-parameter-checking.rst @@ -0,0 +1,79 @@ +Checking Parameter Counts +------------------------- + +.. index:: parameter count checking + +In a programming language that allows for generic and extensible +arguments, what should we do when a function parameter list does not +match any of the patterns of a particular builtin function specification? + +In WMA and systems with rewrite rules, often what is done is the +expression is returned unchanged. This allows users to write rewrite rules +involving patterns that are not covered by your code. + +For example: + +.. code-block:: mathematica + + In[1]:= Hello[] = "Hi, Rocky!"; + + In[2]= Hello[] + Out[2]= "Hi, Rocky!" + +Above, in essence we've added form of *Hello* that does not need a parameter. +Instead, it provides a default value when none is given. + +Pretty convenient and slick huh? Yes, but it can also lead to +confusion in trying to find out where a problem is coming from since +the definition can be spread to from many sources of definitions and +rewrite rules which might be mentioned or part of other definitions: + +.. code-block:: mathematica + + In[3]:= Rocky[Hello[]] = "Hi again, Rocky"; + + In[3]= Rocky[Hello[]] + Out[3]= "Hi again, Rocky" + +Notice that the *In[3]* assignment takes place before the *In[1]* +assignment. Here, these rewrite rules are in close proximity, but +they could be spread out over time or over different files. + +To help you figure how what evaluation is rewriting, see *TraceEvaluation*. + +Many times though, at least for me and other simple-minded users, when +I type a builtin-function with the wrong number of parameters, it is +helpful to get an error message rather than the expression unchanged. + +Generally, I do not expecting (or hope not to expect) some other clever rewrite rule, +doing strange things. + +So, it is sometimes helpful when writing a builtin function, +to specify the number of parameters that are allowed, and give a standard +message when the parameter counts do not match. This can be done setting class variables +``eval_error`` and ``expected_args`` of a builtin function derived from ``Builtin``. + +Here is an example: + +.. code-block:: python + + class Hello(Builtin): + + # Set checking that the number of arguments required to exactly one. + eval_error = Builtin.generic_argument_error + expected_args = 1 + + +Now when I call *Hello* without any arguments you will see:: + + In[4]:= Hello[] + Hello::argx: Hello called with 0 arguments; 1 argument is expected. + +The value of ``expected_args`` does not have to be an integer. It can be a range, like: + +* ``range(5)`` one to four arguments +* ``range(2,4)``: two or three arguments + +or a set: + +* ``{1, 3, 5}`` one, three, or five arguments diff --git a/docs/extending/developing-code/extending/tutorial/2-help-markup.rst b/docs/extending/developing-code/extending/tutorial/3-help-markup.rst similarity index 100% rename from docs/extending/developing-code/extending/tutorial/2-help-markup.rst rename to docs/extending/developing-code/extending/tutorial/3-help-markup.rst diff --git a/docs/extending/developing-code/extending/tutorial/3-test-markup.rst b/docs/extending/developing-code/extending/tutorial/4-test-markup.rst similarity index 100% rename from docs/extending/developing-code/extending/tutorial/3-test-markup.rst rename to docs/extending/developing-code/extending/tutorial/4-test-markup.rst diff --git a/docs/extending/developing-code/extending/tutorial/4-patterns.rst b/docs/extending/developing-code/extending/tutorial/5-patterns.rst similarity index 100% rename from docs/extending/developing-code/extending/tutorial/4-patterns.rst rename to docs/extending/developing-code/extending/tutorial/5-patterns.rst diff --git a/docs/extending/developing-code/extending/tutorial/5-rules.rst b/docs/extending/developing-code/extending/tutorial/6-rules.rst similarity index 100% rename from docs/extending/developing-code/extending/tutorial/5-rules.rst rename to docs/extending/developing-code/extending/tutorial/6-rules.rst diff --git a/docs/extending/developing-code/extending/tutorial/6-attributes.rst b/docs/extending/developing-code/extending/tutorial/7-attributes.rst similarity index 100% rename from docs/extending/developing-code/extending/tutorial/6-attributes.rst rename to docs/extending/developing-code/extending/tutorial/7-attributes.rst diff --git a/docs/extending/developing-code/extending/tutorial/7-warnings.rst b/docs/extending/developing-code/extending/tutorial/7-warnings.rst deleted file mode 100644 index 0830d62..0000000 --- a/docs/extending/developing-code/extending/tutorial/7-warnings.rst +++ /dev/null @@ -1,42 +0,0 @@ -Emitting warnings ------------------ - -Sometimes things go wrong. When things go wrong, we should report an error to -our users. But how can one emit a warning from inside an evaluator? - -Warnings in Mathics3 can be specified via the ``messages`` class field. The -``messages`` class field is a dictionary whose keys are the names of possible -warning messages and whose values are template warning messages. For example, -we may want to display a warning when our users pass something other than a -string to ``Hello``: - -.. code-block:: python - - from typing import Optional - from mathics.builtin.base import Builtin, String - from mathics.core.evaluation import Evaluation - - class Hello(Builtin): - """ -
-
Hello[$person$] -
An example function in a Python-importable Mathics3 module. -
- >> Hello["World"] - = Hello, World! - """ - - messages = { - 'nstr': '`1` is not a string', - } - - def eval(self, person: String, evaluation: Evaluation) -> Optional[String]: - "Hello[person_]" - - if not person.has_form('String'): - return evaluation.message('Hello', 'nstr', person) - - return String(f"Hello, {person.value}!") - -In this case, calling ``Hello[45]`` will emit the warning ``nstr: 45 -is not a string``. diff --git a/docs/extending/developing-code/extending/tutorial/8-warnings.rst b/docs/extending/developing-code/extending/tutorial/8-warnings.rst new file mode 100644 index 0000000..033581a --- /dev/null +++ b/docs/extending/developing-code/extending/tutorial/8-warnings.rst @@ -0,0 +1,64 @@ +Emitting Warnings and Errors +---------------------------- + +Previously, in :ref:`Checking Parameter Counts`, we showed how to give +and error when giving the wrong number of parameters for a function. + +But other things can wrong, too. How can one emit an error from inside an evaluator? + +Warnings and error messages in Mathics3 can be specified via the ``messages`` class field. The +``messages`` class field is a dictionary whose keys are the names of possible +warning messages and whose values are template warning messages. For example, +we may want to display a warning when our users pass something other than a +string to ``Hello``: + +.. code-block:: python + + from typing import Optional + from mathics.builtin.base import Builtin, String + from mathics.core.evaluation import Evaluation + + class Hello(Builtin): + """ +
+
Hello[$person$] +
An example function in a Python-importable Mathics3 module. +
+ >> Hello["World"] + = Hello, World! + """ + + messages = { + 'nstr': '`1` is not a string', + } + + def eval(self, person, evaluation: Evaluation) -> Optional[String]: + "Hello[person_]" + + if not isnstance(person, String): + return evaluation.message('Hello', 'nstr', person) + + return String(f"Hello, {person.value}!") + +In this case, calling ``Hello[45]`` will emit the warning ``nstr: 45 +is not a string``. Since it is inside a Python ``return`` statement, +the code does not continue. However, the expression will be returned unevaluated. + +If you want to indicate a *failure*, then return ``SymbolFailed``: + + +.. code-block:: python + + ... + from mathics.systemsymbols import SymbolFailed + + class Hello(Builtin): + ... + def eval(self, person, evaluation: Evaluation) -> Optional[String] | SymbolNone: + "Hello[person_]" + + if not isinstance(person, String): + evaluation.message("Hello", "nstr", person) + return SymbolFailed + + ...