mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	codegen: Lambda improvements (#1476)
* Use #line directives in generated C++ code for lambdas The #line directive in gcc is meant specifically for pieces of imported code included in generated code, exactly what happens with lambdas in the yaml files: https://gcc.gnu.org/onlinedocs/cpp/Line-Control.html With this change, if I add the following at line 165 of kithen.yaml: - lambda: undefined_var == 5; then "$ esphome kitchen.yaml compile" shows the following: INFO Reading configuration kitchen.yaml... INFO Generating C++ source... INFO Compiling app... INFO Running: platformio run -d kitchen <...> Compiling .pioenvs/kitchen/src/main.cpp.o kitchen.yaml: In lambda function: kitchen.yaml:165:7: error: 'undefined_var' was not declared in this scope *** [.pioenvs/kitchen/src/main.cpp.o] Error 1 == [FAILED] Took 2.37 seconds == * Silence gcc warning on multiline macros in lambdas When the \ is used at the end of the C++ source in a lambda (line continuation, often used in preprocessor macros), esphome will copy that into main.cpp once as code and once as a // commment. gcc will complain about the multiline commment: Compiling .pioenvs/kitchen/src/main.cpp.o kitchen.yaml:640:3: warning: multi-line comment [-Wcomment] Try to replace the \ with a "<cont>" for lack of a better idea.
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							52c67d715b
						
					
				
				
					commit
					c7c71089ce
				
			| @@ -17,9 +17,10 @@ from esphome.const import ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TO | |||||||
|     CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \ |     CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \ | ||||||
|     CONF_TYPE, CONF_PACKAGES |     CONF_TYPE, CONF_PACKAGES | ||||||
| from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ | from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ | ||||||
|     TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes |     TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes, DocumentLocation | ||||||
| from esphome.helpers import list_starts_with, add_class_to_obj | from esphome.helpers import list_starts_with, add_class_to_obj | ||||||
| from esphome.voluptuous_schema import _Schema | from esphome.voluptuous_schema import _Schema | ||||||
|  | from esphome.yaml_util import ESPHomeDataBase | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -982,7 +983,11 @@ LAMBDA_ENTITY_ID_PROG = re.compile(r'id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\) | |||||||
| def lambda_(value): | def lambda_(value): | ||||||
|     """Coerce this configuration option to a lambda.""" |     """Coerce this configuration option to a lambda.""" | ||||||
|     if not isinstance(value, Lambda): |     if not isinstance(value, Lambda): | ||||||
|         value = Lambda(string_strict(value)) |         start_mark = None | ||||||
|  |         if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: | ||||||
|  |             start_mark = DocumentLocation.copy(value.esp_range.start_mark) | ||||||
|  |             start_mark.line += value.content_offset | ||||||
|  |         value = Lambda(string_strict(value), start_mark) | ||||||
|     entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) |     entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) | ||||||
|     if len(entity_id_parts) != 1: |     if len(entity_id_parts) != 1: | ||||||
|         entity_ids = ' '.join("'{}'".format(entity_id_parts[i]) |         entity_ids = ' '.join("'{}'".format(entity_id_parts[i]) | ||||||
|   | |||||||
| @@ -227,7 +227,7 @@ LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)') | |||||||
|  |  | ||||||
|  |  | ||||||
| class Lambda: | class Lambda: | ||||||
|     def __init__(self, value): |     def __init__(self, value, start_mark=None): | ||||||
|         # pylint: disable=protected-access |         # pylint: disable=protected-access | ||||||
|         if isinstance(value, Lambda): |         if isinstance(value, Lambda): | ||||||
|             self._value = value._value |             self._value = value._value | ||||||
| @@ -235,6 +235,7 @@ class Lambda: | |||||||
|             self._value = value |             self._value = value | ||||||
|         self._parts = None |         self._parts = None | ||||||
|         self._requires_ids = None |         self._requires_ids = None | ||||||
|  |         self._source_location = start_mark | ||||||
|  |  | ||||||
|     # https://stackoverflow.com/a/241506/229052 |     # https://stackoverflow.com/a/241506/229052 | ||||||
|     def comment_remover(self, text): |     def comment_remover(self, text): | ||||||
| @@ -277,6 +278,10 @@ class Lambda: | |||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return f'Lambda<{self.value}>' |         return f'Lambda<{self.value}>' | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def source_location(self): | ||||||
|  |         return self._source_location | ||||||
|  |  | ||||||
|  |  | ||||||
| class ID: | class ID: | ||||||
|     def __init__(self, id, is_declaration=False, type=None, is_manual=None): |     def __init__(self, id, is_declaration=False, type=None, is_manual=None): | ||||||
| @@ -334,9 +339,21 @@ class DocumentLocation: | |||||||
|             mark.column |             mark.column | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def copy(cls, location): | ||||||
|  |         return cls( | ||||||
|  |             location.document, | ||||||
|  |             location.line, | ||||||
|  |             location.column | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return f'{self.document} {self.line}:{self.column}' |         return f'{self.document} {self.line}:{self.column}' | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def as_line_directive(self): | ||||||
|  |         return f'#line {self.line + 1} "{self.document}"' | ||||||
|  |  | ||||||
|  |  | ||||||
| class DocumentRange: | class DocumentRange: | ||||||
|     def __init__(self, start_mark: DocumentLocation, end_mark: DocumentLocation): |     def __init__(self, start_mark: DocumentLocation, end_mark: DocumentLocation): | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import abc | import abc | ||||||
| import inspect | import inspect | ||||||
| import math | import math | ||||||
|  | import re | ||||||
|  |  | ||||||
| # pylint: disable=unused-import, wrong-import-order | # pylint: disable=unused-import, wrong-import-order | ||||||
| from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence | from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence | ||||||
| @@ -188,13 +189,14 @@ class ParameterListExpression(Expression): | |||||||
|  |  | ||||||
|  |  | ||||||
| class LambdaExpression(Expression): | class LambdaExpression(Expression): | ||||||
|     __slots__ = ("parts", "parameters", "capture", "return_type") |     __slots__ = ("parts", "parameters", "capture", "return_type", "source") | ||||||
|  |  | ||||||
|     def __init__(self, parts, parameters, capture: str = '=', return_type=None): |     def __init__(self, parts, parameters, capture: str = '=', return_type=None, source=None): | ||||||
|         self.parts = parts |         self.parts = parts | ||||||
|         if not isinstance(parameters, ParameterListExpression): |         if not isinstance(parameters, ParameterListExpression): | ||||||
|             parameters = ParameterListExpression(*parameters) |             parameters = ParameterListExpression(*parameters) | ||||||
|         self.parameters = parameters |         self.parameters = parameters | ||||||
|  |         self.source = source | ||||||
|         self.capture = capture |         self.capture = capture | ||||||
|         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 | ||||||
|  |  | ||||||
| @@ -202,7 +204,10 @@ class LambdaExpression(Expression): | |||||||
|         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}' | ||||||
|         cpp += f' {{\n{self.content}\n}}' |         cpp += ' {\n' | ||||||
|  |         if self.source is not None: | ||||||
|  |             cpp += f'{self.source.as_line_directive}\n' | ||||||
|  |         cpp += f'{self.content}\n}}' | ||||||
|         return indent_all_but_first_and_last(cpp) |         return indent_all_but_first_and_last(cpp) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @@ -360,7 +365,7 @@ class LineComment(Statement): | |||||||
|         self.value = value |         self.value = value | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         parts = self.value.split('\n') |         parts = re.sub(r'\\\s*\n', r'<cont>\n', self.value, re.MULTILINE).split('\n') | ||||||
|         parts = [f'// {x}' for x in parts] |         parts = [f'// {x}' for x in parts] | ||||||
|         return '\n'.join(parts) |         return '\n'.join(parts) | ||||||
|  |  | ||||||
| @@ -555,7 +560,7 @@ def process_lambda( | |||||||
|         else: |         else: | ||||||
|             parts[i * 3 + 1] = var |             parts[i * 3 + 1] = var | ||||||
|         parts[i * 3 + 2] = '' |         parts[i * 3 + 2] = '' | ||||||
|     yield LambdaExpression(parts, parameters, capture, return_type) |     yield LambdaExpression(parts, parameters, capture, return_type, value.source_location) | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_template(value): | def is_template(value): | ||||||
|   | |||||||
| @@ -11,7 +11,8 @@ import yaml.constructor | |||||||
|  |  | ||||||
| from esphome import core | from esphome import core | ||||||
| from esphome.config_helpers import read_config_file | from esphome.config_helpers import read_config_file | ||||||
| from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange | from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, \ | ||||||
|  |     DocumentRange, DocumentLocation | ||||||
| from esphome.helpers import add_class_to_obj | from esphome.helpers import add_class_to_obj | ||||||
| from esphome.util import OrderedDict, filter_yaml_files | from esphome.util import OrderedDict, filter_yaml_files | ||||||
|  |  | ||||||
| @@ -30,9 +31,16 @@ class ESPHomeDataBase: | |||||||
|     def esp_range(self): |     def esp_range(self): | ||||||
|         return getattr(self, '_esp_range', None) |         return getattr(self, '_esp_range', None) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def content_offset(self): | ||||||
|  |         return getattr(self, '_content_offset', 0) | ||||||
|  |  | ||||||
|     def from_node(self, node): |     def from_node(self, node): | ||||||
|         # pylint: disable=attribute-defined-outside-init |         # pylint: disable=attribute-defined-outside-init | ||||||
|         self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark) |         self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark) | ||||||
|  |         if isinstance(node, yaml.ScalarNode): | ||||||
|  |             if node.style is not None and node.style in '|>': | ||||||
|  |                 self._content_offset = 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| class ESPForceValue: | class ESPForceValue: | ||||||
| @@ -257,7 +265,10 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | |||||||
|  |  | ||||||
|     @_add_data_ref |     @_add_data_ref | ||||||
|     def construct_lambda(self, node): |     def construct_lambda(self, node): | ||||||
|         return Lambda(str(node.value)) |         start_mark = DocumentLocation.from_mark(node.start_mark) | ||||||
|  |         if node.style is not None and node.style in '|>': | ||||||
|  |             start_mark.line += 1 | ||||||
|  |         return Lambda(str(node.value), start_mark) | ||||||
|  |  | ||||||
|     @_add_data_ref |     @_add_data_ref | ||||||
|     def construct_force(self, node): |     def construct_force(self, node): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user