godot icon indicating copy to clipboard operation
godot copied to clipboard

Expression.parse() returns OK with undefined variables

Open lostminds opened this issue 1 year ago • 3 comments

Tested versions

4.2.1.stable

System information

macOS 14.2.1

Issue description

The Expression class allows parsing of mathematical expressions, allowing optionally to include variables that can be used. Using variables in the expression that are not defined will cause the expression to fail on expression.execute() as expected. However, using expression.parse(), which I understand should evaluate if the expression is before executing, returns OK even if undefined variables are present.

This mean that it's difficult to check if an expression includes undefined variables (for example based on user input) before calling expression.execute(), and execute will throw an error if there are such undefined variables:

E 0:00:08:0338   Script.gd:93 @ expression_test(): self can't be used because instance is null (not passed)
  <C++ Error>    Condition "p_show_error" is true. Returning: Variant()
  <C++ Source>   core/math/expression.cpp:1507 @ execute()
  <Stack Trace>  Script.gd:93 @ expression_test()
                 Script.gd:101 @ parse_value()
                 ValueField.gd:60 @ on_value_field_text_submitted()

This same error "self can't be used because instance is null (not passed)" is also returned in expression.get_error_text() and is not very helpful in my mind, as it does not mention the offending undefined variable, and instead makes it seem like you've referenced self, which is not the case and may also be a bug?

Steps to reproduce

Set up the following function:

func expression_test(string:String):
	var expression:Expression = Expression.new()
	print("expression test with string: '"+string+"'")
	var parse_error = expression.parse(string)
	print("parse_error: "+str(parse_error))
	if (parse_error == OK):
		var parse_result = expression.execute()
		print("parse_result: "+str(parse_result))
		print("has_execute_failed: "+str(expression.has_execute_failed()))
	print("get_error_text: "+expression.get_error_text())

And then test it with:

expression_test("1+1") #simple test expression
expression_test("1+a") #expression with undefined variable!

Generates output:

expression test with string: '1+1'
parse_error: 0
parse_result: 2
has_execute_failed: false
get_error_text: 
expression test with string: '1+a'
parse_error: 0
parse_result: <null>
has_execute_failed: true
get_error_text: self can't be used because instance is null (not passed)

Along with the error above being thrown when calling expression.execute() for "1+a"

Expected outcome for me would be that expression.parse("1+a") would return an error as it contains an undefined variable, and then that expression.get_error_text() would return something like "undefined variable 'a'"

Minimal reproduction project (MRP)

See code above

lostminds avatar Jan 04 '24 09:01 lostminds

I think parse() assumes that undefined variables will be attributes of the base_instance object passed to execute().

If you pass a base_instance the error message is a bit more helpful: Invalid named index 'a' for base type Object.

So parse() should have a base_instance parameter too to be able to check for undefined variables/attributes/methods.

xorblo-doitus avatar Jan 21 '24 07:01 xorblo-doitus

So parse() should have a base_instance parameter too to be able to check for undefined variables/attributes/methods.

Yes, that could be a good solution. And critically allow the (default) case of passing null as the base_instance object in parse() to give correct parsing errors for the case of not passing a base_instance object in execute().

lostminds avatar Jan 21 '24 10:01 lostminds

It's definitely intended that unknown identifiers be kept for execute to take care of. Unless you pass the base_instance to parse as well, it's fine as is.

Tomas-Domas avatar Oct 02 '24 01:10 Tomas-Domas