Create callable from string?

Godot Version

4.3

Question

Is there a way to create a callable by defining it inside of a string?
e.g.

#This is a string!
@export var skibidi_callable_string: String = "func(s: String): print("skibidi " + s)"

#This is a callable!
var skibidi_callable: Callable = string_to_callable(skibidi_callable_string)

func _ready() -> void:
    skibidi_callable.call("hello world!")
    #Should print "skibidi hello world!"

func string_to_callable(string: String) -> Callable:
    #???

Is there any “string_to_callable” forbidden function or any way to create it?

Thanks in advance!

No, but you can use call/callv/call_deferred instead.

Yeah, it’s just easier than what you are doing.

here’s the doc entry.

var  string_name_of_function : = "first_bit"+"second_bit"+"third_bit"
var my_function = Callable(node_with_function, string_name_of_function)

my_function.call()

I was thinking about creating a new function only based on a string
However I had an idea:
Creating a new script based on the string so that it has the function defined in the string into itself, then attach it to a ref counted or a resource and call the function from it. I’m gonna try it and see if it works!

It worked!

class FuncHolder:
	var ref: RefCounted
	var func_script: Script

	func _init(new_ref: RefCounted, new_script: Script) -> void:
		ref = new_ref
		func_script = new_script

var functions: Array[FuncHolder]

func toot() -> void:
	print("toot!")

func _ready() -> void:
	name = "uvuguggi"

func string_to_callable(func_string: String) -> Callable:
	var execute_string_resource := RefCounted.new()
	var execute_string_script := GDScript.new()
	var new_source_code: String = "var base_node: Node\n"
	if not func_string.begins_with("\n"):
		new_source_code += "\n"
	new_source_code += func_string
	execute_string_script.set_source_code(new_source_code)
	assert(execute_string_script.reload() == OK)
	execute_string_resource = RefCounted.new()
	execute_string_resource.set_script(execute_string_script)
	execute_string_resource.base_node = self
	var func_name = func_string.right(-6).get_slice("(", 0)
	var this_func := FuncHolder.new(execute_string_resource, execute_string_script)
	functions.append(this_func)
	return Callable(execute_string_resource, func_name)

Usage example:

extends Node

class FuncHolder:
	var ref: RefCounted
	var func_script: Script

	func _init(new_ref: RefCounted, new_script: Script) -> void:
		ref = new_ref
		func_script = new_script

var functions: Array[FuncHolder]

func toot() -> void:
	print("toot!")

func _ready() -> void:
	name = "uvuguggi"

	var skibidi_func_string: String = \
"""
func skibidi_print(what: String) -> void:
	print(base_node.name+" printed: skibidi "+what)
"""
	var skibidi_callable: Callable = string_to_callable(skibidi_func_string)
	skibidi_callable.call("hello world")
	skibidi_callable.call("callable")

	var tooting_func_string: String = \
"""
func toot_base_node() -> void:
	base_node.toot()
"""
	var tooting_callable: Callable = string_to_callable(tooting_func_string)
	tooting_callable.call()

func string_to_callable(func_string: String) -> Callable:
	var execute_string_resource := RefCounted.new()
	var execute_string_script := GDScript.new()
	var new_source_code: String = "var base_node: Node\n"
	if not func_string.begins_with("\n"):
		new_source_code += "\n"
	new_source_code += func_string
	execute_string_script.set_source_code(new_source_code)
	assert(execute_string_script.reload() == OK)
	execute_string_resource = RefCounted.new()
	execute_string_resource.set_script(execute_string_script)
	execute_string_resource.base_node = self
	var func_name = func_string.right(-6).get_slice("(", 0)
	var this_func := FuncHolder.new(execute_string_resource, execute_string_script)
	functions.append(this_func)
	return Callable(execute_string_resource, func_name)

prints:

uvuguggi printed: skibidi hello world
uvuguggi printed: skibidi callable
toot!

It might be best used added to an autoload script

You should however change the “base_node” property of the ref

like this:

extends Node

class FuncHolder:
	var ref: RefCounted
	var func_script: Script

	func _init(new_ref: RefCounted, new_script: Script) -> void:
		ref = new_ref
		func_script = new_script

var functions: Array[FuncHolder]

func string_to_callable(func_string: String, base_node: Node = null) -> Callable:
	var execute_string_resource := RefCounted.new()
	var execute_string_script := GDScript.new()
	var new_source_code: String = ""
	if base_node:
		new_source_code = "var base_node: Node\n"
		new_source_code += func_string
	new_source_code += func_string
	
	execute_string_script.set_source_code(new_source_code)

	assert(execute_string_script.reload() == OK)

	execute_string_resource = RefCounted.new()
	execute_string_resource.set_script(execute_string_script)

	if base_node:
		execute_string_resource.base_node = self

	var func_name = func_string.get_slice(" ", 1).get_slice("(", 0)

	var this_func := FuncHolder.new(execute_string_resource, execute_string_script)

	functions.append(this_func)

	return Callable(execute_string_resource, func_name)