mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +00:00
Optimize stateless lambdas to use function pointers (#11551)
This commit is contained in:
@@ -16,7 +16,12 @@ from esphome.const import (
|
|||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
)
|
)
|
||||||
from esphome.core import ID
|
from esphome.core import ID
|
||||||
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
|
from esphome.cpp_generator import (
|
||||||
|
LambdaExpression,
|
||||||
|
MockObj,
|
||||||
|
MockObjClass,
|
||||||
|
TemplateArgsType,
|
||||||
|
)
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
@@ -87,6 +92,7 @@ def validate_potentially_or_condition(value):
|
|||||||
|
|
||||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||||
|
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
|
||||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||||
@@ -97,9 +103,40 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
|||||||
Automation = cg.esphome_ns.class_("Automation")
|
Automation = cg.esphome_ns.class_("Automation")
|
||||||
|
|
||||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||||
|
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
|
||||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
|
def new_lambda_pvariable(
|
||||||
|
id_obj: ID,
|
||||||
|
lambda_expr: LambdaExpression,
|
||||||
|
stateless_class: MockObjClass,
|
||||||
|
template_arg: cg.TemplateArguments | None = None,
|
||||||
|
) -> MockObj:
|
||||||
|
"""Create Pvariable for lambda, using stateless class if applicable.
|
||||||
|
|
||||||
|
Combines ID selection and Pvariable creation in one call. For stateless
|
||||||
|
lambdas (empty capture), uses function pointer instead of std::function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id_obj: The ID object (action_id, condition_id, or filter_id)
|
||||||
|
lambda_expr: The lambda expression object
|
||||||
|
stateless_class: The stateless class to use for stateless lambdas
|
||||||
|
template_arg: Optional template arguments (for actions/conditions)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created Pvariable
|
||||||
|
"""
|
||||||
|
# For stateless lambdas, use function pointer instead of std::function
|
||||||
|
if lambda_expr.capture == "":
|
||||||
|
id_obj = id_obj.copy()
|
||||||
|
id_obj.type = stateless_class
|
||||||
|
|
||||||
|
if template_arg is not None:
|
||||||
|
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
|
||||||
|
return cg.new_Pvariable(id_obj, lambda_expr)
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
if extra_schema is None:
|
if extra_schema is None:
|
||||||
extra_schema = {}
|
extra_schema = {}
|
||||||
@@ -240,7 +277,9 @@ async def lambda_condition_to_code(
|
|||||||
args: TemplateArgsType,
|
args: TemplateArgsType,
|
||||||
) -> MockObj:
|
) -> MockObj:
|
||||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
return new_lambda_pvariable(
|
||||||
|
condition_id, lambda_, StatelessLambdaCondition, template_arg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_condition(
|
@register_condition(
|
||||||
@@ -406,7 +445,7 @@ async def lambda_action_to_code(
|
|||||||
args: TemplateArgsType,
|
args: TemplateArgsType,
|
||||||
) -> MockObj:
|
) -> MockObj:
|
||||||
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
|
||||||
|
|
||||||
|
|
||||||
@register_action(
|
@register_action(
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
|
|||||||
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
||||||
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
||||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||||
|
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
|
||||||
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
|
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
@@ -299,7 +300,7 @@ async def lambda_filter_to_code(config, filter_id):
|
|||||||
lambda_ = await cg.process_lambda(
|
lambda_ = await cg.process_lambda(
|
||||||
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
||||||
)
|
)
|
||||||
return cg.new_Pvariable(filter_id, lambda_)
|
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||||
|
|
||||||
|
|
||||||
@register_filter(
|
@register_filter(
|
||||||
|
|||||||
@@ -111,6 +111,21 @@ class LambdaFilter : public Filter {
|
|||||||
std::function<optional<bool>(bool)> f_;
|
std::function<optional<bool>(bool)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Optimized lambda filter for stateless lambdas (no capture).
|
||||||
|
*
|
||||||
|
* Uses function pointer instead of std::function to reduce memory overhead.
|
||||||
|
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||||
|
*/
|
||||||
|
class StatelessLambdaFilter : public Filter {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
|
||||||
|
|
||||||
|
optional<bool> new_value(bool value) override { return this->f_(value); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
optional<bool> (*f_)(bool);
|
||||||
|
};
|
||||||
|
|
||||||
class SettleFilter : public Filter, public Component {
|
class SettleFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value) override;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import LambdaAction
|
from esphome.automation import LambdaAction, StatelessLambdaAction
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
||||||
from esphome.components.esp32.const import (
|
from esphome.components.esp32.const import (
|
||||||
@@ -430,7 +430,9 @@ async def logger_log_action_to_code(config, action_id, template_arg, args):
|
|||||||
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
|
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
|
||||||
|
|
||||||
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
return automation.new_lambda_pvariable(
|
||||||
|
action_id, lambda_, StatelessLambdaAction, template_arg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
@@ -455,7 +457,9 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
|
|||||||
text = str(cg.statement(logger.set_log_level(level)))
|
text = str(cg.statement(logger.set_log_level(level)))
|
||||||
|
|
||||||
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
return automation.new_lambda_pvariable(
|
||||||
|
action_id, lambda_, StatelessLambdaAction, template_arg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ ExponentialMovingAverageFilter = sensor_ns.class_(
|
|||||||
)
|
)
|
||||||
ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component)
|
ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component)
|
||||||
LambdaFilter = sensor_ns.class_("LambdaFilter", Filter)
|
LambdaFilter = sensor_ns.class_("LambdaFilter", Filter)
|
||||||
|
StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter)
|
||||||
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
|
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
|
||||||
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
|
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
|
||||||
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
|
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
|
||||||
@@ -573,7 +574,7 @@ async def lambda_filter_to_code(config, filter_id):
|
|||||||
lambda_ = await cg.process_lambda(
|
lambda_ = await cg.process_lambda(
|
||||||
config, [(float, "x")], return_type=cg.optional.template(float)
|
config, [(float, "x")], return_type=cg.optional.template(float)
|
||||||
)
|
)
|
||||||
return cg.new_Pvariable(filter_id, lambda_)
|
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||||
|
|
||||||
|
|
||||||
DELTA_SCHEMA = cv.Schema(
|
DELTA_SCHEMA = cv.Schema(
|
||||||
|
|||||||
@@ -296,6 +296,21 @@ class LambdaFilter : public Filter {
|
|||||||
lambda_filter_t lambda_filter_;
|
lambda_filter_t lambda_filter_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Optimized lambda filter for stateless lambdas (no capture).
|
||||||
|
*
|
||||||
|
* Uses function pointer instead of std::function to reduce memory overhead.
|
||||||
|
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||||
|
*/
|
||||||
|
class StatelessLambdaFilter : public Filter {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaFilter(optional<float> (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {}
|
||||||
|
|
||||||
|
optional<float> new_value(float value) override { return this->lambda_filter_(value); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
optional<float> (*lambda_filter_)(float);
|
||||||
|
};
|
||||||
|
|
||||||
/// A simple filter that adds `offset` to each value it receives.
|
/// A simple filter that adds `offset` to each value it receives.
|
||||||
class OffsetFilter : public Filter {
|
class OffsetFilter : public Filter {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
|
|||||||
# Filters
|
# Filters
|
||||||
Filter = text_sensor_ns.class_("Filter")
|
Filter = text_sensor_ns.class_("Filter")
|
||||||
LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter)
|
LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter)
|
||||||
|
StatelessLambdaFilter = text_sensor_ns.class_("StatelessLambdaFilter", Filter)
|
||||||
ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter)
|
ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter)
|
||||||
ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
|
ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
|
||||||
AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
|
AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
|
||||||
@@ -70,7 +71,7 @@ async def lambda_filter_to_code(config, filter_id):
|
|||||||
lambda_ = await cg.process_lambda(
|
lambda_ = await cg.process_lambda(
|
||||||
config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string)
|
config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string)
|
||||||
)
|
)
|
||||||
return cg.new_Pvariable(filter_id, lambda_)
|
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {})
|
@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {})
|
||||||
|
|||||||
@@ -62,6 +62,21 @@ class LambdaFilter : public Filter {
|
|||||||
lambda_filter_t lambda_filter_;
|
lambda_filter_t lambda_filter_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Optimized lambda filter for stateless lambdas (no capture).
|
||||||
|
*
|
||||||
|
* Uses function pointer instead of std::function to reduce memory overhead.
|
||||||
|
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||||
|
*/
|
||||||
|
class StatelessLambdaFilter : public Filter {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {}
|
||||||
|
|
||||||
|
optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
optional<std::string> (*lambda_filter_)(std::string);
|
||||||
|
};
|
||||||
|
|
||||||
/// A simple filter that converts all text to uppercase
|
/// A simple filter that converts all text to uppercase
|
||||||
class ToUpperFilter : public Filter {
|
class ToUpperFilter : public Filter {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -79,6 +79,18 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
|
|||||||
std::function<bool(Ts...)> f_;
|
std::function<bool(Ts...)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Optimized lambda condition for stateless lambdas (no capture).
|
||||||
|
/// Uses function pointer instead of std::function to reduce memory overhead.
|
||||||
|
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||||
|
template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
|
||||||
|
bool check(Ts... x) override { return this->f_(x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool (*f_)(Ts...);
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
|
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
|
||||||
public:
|
public:
|
||||||
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
|
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
|
||||||
@@ -190,6 +202,19 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> {
|
|||||||
std::function<void(Ts...)> f_;
|
std::function<void(Ts...)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Optimized lambda action for stateless lambdas (no capture).
|
||||||
|
/// Uses function pointer instead of std::function to reduce memory overhead.
|
||||||
|
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||||
|
template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->f_(x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void (*f_)(Ts...);
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Ts> class IfAction : public Action<Ts...> {
|
template<typename... Ts> class IfAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
|
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
|
||||||
|
|||||||
@@ -198,6 +198,8 @@ class LambdaExpression(Expression):
|
|||||||
self.return_type = safe_exp(return_type) if return_type is not None else None
|
self.return_type = safe_exp(return_type) if return_type is not None else None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
# Stateless lambdas (empty capture) implicitly convert to function pointers
|
||||||
|
# when assigned to function pointer types - no unary + needed
|
||||||
cpp = f"[{self.capture}]({self.parameters})"
|
cpp = f"[{self.capture}]({self.parameters})"
|
||||||
if self.return_type is not None:
|
if self.return_type is not None:
|
||||||
cpp += f" -> {self.return_type}"
|
cpp += f" -> {self.return_type}"
|
||||||
@@ -700,6 +702,12 @@ async def process_lambda(
|
|||||||
parts[i * 3 + 1] = var
|
parts[i * 3 + 1] = var
|
||||||
parts[i * 3 + 2] = ""
|
parts[i * 3 + 2] = ""
|
||||||
|
|
||||||
|
# All id() references are global variables in generated C++ code.
|
||||||
|
# Global variables should not be captured - they're accessible everywhere.
|
||||||
|
# Use empty capture instead of capture-by-value.
|
||||||
|
if capture == "=":
|
||||||
|
capture = ""
|
||||||
|
|
||||||
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
||||||
location = value.esp_range.start_mark
|
location = value.esp_range.start_mark
|
||||||
location.line += value.content_offset
|
location.line += value.content_offset
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main):
|
|||||||
|
|
||||||
def test_text_config_lamda_is_set(generate_main):
|
def test_text_config_lamda_is_set(generate_main):
|
||||||
"""
|
"""
|
||||||
Test if lambda is set for lambda mode
|
Test if lambda is set for lambda mode (optimized with stateless lambda)
|
||||||
"""
|
"""
|
||||||
# Given
|
# Given
|
||||||
|
|
||||||
@@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main):
|
|||||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||||
|
|
||||||
# Then
|
# Then
|
||||||
assert "it_4->set_template([=]() -> esphome::optional<std::string> {" in main_cpp
|
assert "it_4->set_template([]() -> esphome::optional<std::string> {" in main_cpp
|
||||||
assert 'return std::string{"Hello"};' in main_cpp
|
assert 'return std::string{"Hello"};' in main_cpp
|
||||||
|
|||||||
@@ -173,6 +173,61 @@ class TestLambdaExpression:
|
|||||||
"}"
|
"}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_str__stateless_no_return(self):
|
||||||
|
"""Test stateless lambda (empty capture) generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
('ESP_LOGD("main", "Test message");',),
|
||||||
|
(), # No parameters
|
||||||
|
"", # Empty capture (stateless)
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == ('[]() {\n ESP_LOGD("main", "Test message");\n}')
|
||||||
|
|
||||||
|
def test_str__stateless_with_return(self):
|
||||||
|
"""Test stateless lambda with return type generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
("return global_value > 0;",),
|
||||||
|
(), # No parameters
|
||||||
|
"", # Empty capture (stateless)
|
||||||
|
bool, # Return type
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == ("[]() -> bool {\n return global_value > 0;\n}")
|
||||||
|
|
||||||
|
def test_str__stateless_with_params(self):
|
||||||
|
"""Test stateless lambda with parameters generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
("return foo + bar;",),
|
||||||
|
((int, "foo"), (float, "bar")),
|
||||||
|
"", # Empty capture (stateless)
|
||||||
|
float,
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == (
|
||||||
|
"[](int32_t foo, float bar) -> float {\n return foo + bar;\n}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_str__with_capture(self):
|
||||||
|
"""Test lambda with capture generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
("return captured_var + x;",),
|
||||||
|
((int, "x"),),
|
||||||
|
"captured_var", # Has capture (not stateless)
|
||||||
|
int,
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == (
|
||||||
|
"[captured_var](int32_t x) -> int32_t {\n return captured_var + x;\n}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestLiterals:
|
class TestLiterals:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|||||||
Reference in New Issue
Block a user