Răsfoiți Sursa

Add a page on evaluating expressions using the Expression class

This is a 3.4 backport (with identical contents) of the `master` branch
page, with fixes included.
Hugo Locurcio 3 ani în urmă
părinte
comite
b99deb0558

+ 208 - 0
tutorials/scripting/evaluating_expressions.rst

@@ -0,0 +1,208 @@
+.. _doc_evaluating_expressions:
+
+Evaluating expressions
+======================
+
+Godot provides an :ref:`class_Expression` class you can use to evaluate expressions.
+
+An expression can be:
+
+- A mathematical expression such as ``(2 + 4) * 16/4.0``.
+- A built-in method call like ``deg2rad(90)``.
+- A method call on an user-provided script like ``update_health()``,
+  if ``base_instance`` is set to a value other than ``null`` when calling
+  :ref:`Expression.execute() <class_Expression_method_execute>`.
+
+.. note::
+
+    The Expression class is independent from GDScript.
+    It's available even if you compile Godot with the GDScript module disabled.
+
+Basic usage
+-----------
+
+To evaluate a mathematical expression, use::
+
+    var expression = Expression.new()
+    expression.parse("20 + 10*2 - 5/2.0")
+    var result = expression.execute()
+    print(result)  # 37.5
+
+The following operators are available:
+
++------------------------+-------------------------------------------------------------------------------------+
+| Operator               | Notes                                                                               |
++========================+=====================================================================================+
+| Addition ``+``         | Can also be used to concatenate strings and arrays:                                 |
+|                        | - ``"hello" + " world"`` = ``hello world``                                          |
+|                        | - ``[1, 2] + [3, 4]`` = ``[1, 2, 3, 4]``                                            |
++------------------------+-------------------------------------------------------------------------------------+
+| Subtraction (``-``)    |                                                                                     |
++------------------------+-------------------------------------------------------------------------------------+
+| Multiplication (``*``) |                                                                                     |
++------------------------+-------------------------------------------------------------------------------------+
+| Division (``/``)       | Performs and integer division if both operands are integers.                        |
+|                        | If at least one of them is a floating-point number, returns a floating-point value. |
++------------------------+-------------------------------------------------------------------------------------+
+| Modulo (``%``)         | Returns the remainder of an integer division.                                       |
++------------------------+-------------------------------------------------------------------------------------+
+
+Spaces around operators are optional. Also, keep in mind the usual
+`order of operations <https://en.wikipedia.org/wiki/Order_of_operations>`__
+applies. Use parentheses to override the order of operations if needed.
+
+All the Variant types supported in Godot can be used: integers, floating-point
+numbers, strings, arrays, dictionaries, colors, vectors, …
+
+Arrays and dictionaries can be indexed like in GDScript::
+
+    # Returns 1.
+    [1, 2][0]
+
+    # Returns 3. Negative indices can be used to count from the end of the array.
+    [1, 3][-1]
+
+    # Returns "green".
+    {"favorite_color": "green"}["favorite_color"]
+
+    # All 3 lines below return 7.0 (Vector3 is floating-point).
+    Vector3(5, 6, 7)[2]
+    Vector3(5, 6, 7)["z"]
+    Vector3(5, 6, 7).z
+
+Passing variables to an expression
+----------------------------------
+
+You can pass variables to an expression. These variables will then
+become available in the expression's "context" and will be substituted when used
+in the expression::
+
+    var expression = Expression.new()
+    # Define the variable names first in the second parameter of `parse()`.
+    # In this example, we use `x` for the variable name.
+    expression.parse("20 + 2 * x", ["x"])
+    # Then define the variable values in the first parameter of `execute()`.
+    # Here, `x` is assigned the integer value 5.
+    var result = expression.execute([5])
+    print(result)  # 30
+
+Both the variable names and variable values **must** be specified as an array,
+even if you only define one variable. Also, variable names are **case-sensitive**.
+
+Setting a base instance for the expression
+------------------------------------------
+
+By default, an expression has a base instance of ``null``. This means the
+expression has no base instance associated to it.
+
+When calling :ref:`Expression.execute() <class_Expression_method_execute>`,
+you can set the value of the ``base_instance`` parameter to a specific object
+instance such as ``self``, another script instance or even a singleton::
+
+    func double(number):
+        return number * 2
+
+
+    func _ready():
+        var expression = Expression.new()
+        expression.parse("double(10)")
+
+        # This won't work since we're not passing the current script as the base instance.
+        var result = expression.execute([], null)
+        print(result)  # null
+
+        # This will work since we're passing the current script (i.e. self)
+        # as the base instance.
+        result = expression.execute([], self)
+        print(result)  # 20
+
+Associating a base instance allows doing the following:
+
+- Reference the instance's constants (``const``) in the expression.
+- Reference the instance's member variables (``var``) in the expression.
+- Call methods defined in the instance and use their return values in the expression.
+
+.. warning::
+
+    Setting a base instance to a value other than ``null`` allows referencing
+    constants, member variables, and calling all methods defined in the script
+    attached to the instance. Allowing users to enter expressions may allow
+    cheating in your game, or may even introduce security vulnerabilities if you
+    allow arbitrary clients to run expressions on other players' devices.
+
+Example script
+--------------
+
+The script below demonstrates what the Expression class is capable of::
+
+    const DAYS_IN_YEAR = 365
+    var script_member_variable = 1000
+
+
+    func _ready():
+        # Constant mathexpression.
+        evaluate("2 + 2")
+        # Math expression with variables.
+        evaluate("x + y", ["x", "y"], [60, 100])
+
+        # Call built-in method (hardcoded in the Expression class).
+        evaluate("deg2rad(90)")
+
+        # Call user method (defined in the script).
+        # We can do this because the expression execution is bound to `self`
+        # in the `evaluate()` method.
+        # Since this user method returns a value, we can use it in math expressions.
+        evaluate("call_me() + DAYS_IN_YEAR + script_member_variable")
+        evaluate("call_me(42)")
+        evaluate("call_me('some string')")
+
+
+    func evaluate(command, variable_names = [], variable_values = []) -> void:
+        var expression = Expression.new()
+        var error = expression.parse(command, variable_names)
+        if error != OK:
+            push_error(expression.get_error_text())
+            return
+
+        var result = expression.execute(variable_values, self)
+
+        if not expression.has_execute_failed():
+            print(str(result))
+
+
+    func call_me(argument = null):
+        print("\nYou called 'call_me()' in the expression text.")
+        if argument:
+            print("Argument passed: %s" % argument)
+
+        # The method's return value is also the expression's return value.
+        return 0
+
+The output from the script will be::
+
+    4
+    160
+    1.570796
+
+    You called 'call_me()' in the expression text.
+    1365
+
+    You called 'call_me()' in the expression text.
+    Argument passed: 42
+    0
+
+    You called 'call_me()' in the expression text.
+    Argument passed: some string
+    0
+
+Built-in functions
+------------------
+
+Most methods available in the :ref:`class_@GDScript` scope are available in the
+Expression class, even if no base instance is bound to the expression.
+The same parameters and return types are available.
+
+However, unlike GDScript, parameters are **always required** even if they're
+specified as being optional in the class reference. In contrast, this
+restriction on arguments doesn't apply to user-made functions when you bind a
+base instance to the expression.

+ 1 - 0
tutorials/scripting/index.rst

@@ -43,6 +43,7 @@ below will help you make the most of Godot.
    overridable_functions
    cross_language_scripting
    creating_script_templates
+   evaluating_expressions
    change_scenes_manually
    instancing_with_signals
    pausing_games