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_TYPE, CONF_PACKAGES | ||||
| 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.voluptuous_schema import _Schema | ||||
| from esphome.yaml_util import ESPHomeDataBase | ||||
|  | ||||
| _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): | ||||
|     """Coerce this configuration option to a 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) | ||||
|     if len(entity_id_parts) != 1: | ||||
|         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: | ||||
|     def __init__(self, value): | ||||
|     def __init__(self, value, start_mark=None): | ||||
|         # pylint: disable=protected-access | ||||
|         if isinstance(value, Lambda): | ||||
|             self._value = value._value | ||||
| @@ -235,6 +235,7 @@ class Lambda: | ||||
|             self._value = value | ||||
|         self._parts = None | ||||
|         self._requires_ids = None | ||||
|         self._source_location = start_mark | ||||
|  | ||||
|     # https://stackoverflow.com/a/241506/229052 | ||||
|     def comment_remover(self, text): | ||||
| @@ -277,6 +278,10 @@ class Lambda: | ||||
|     def __repr__(self): | ||||
|         return f'Lambda<{self.value}>' | ||||
|  | ||||
|     @property | ||||
|     def source_location(self): | ||||
|         return self._source_location | ||||
|  | ||||
|  | ||||
| class ID: | ||||
|     def __init__(self, id, is_declaration=False, type=None, is_manual=None): | ||||
| @@ -334,9 +339,21 @@ class DocumentLocation: | ||||
|             mark.column | ||||
|         ) | ||||
|  | ||||
|     @classmethod | ||||
|     def copy(cls, location): | ||||
|         return cls( | ||||
|             location.document, | ||||
|             location.line, | ||||
|             location.column | ||||
|         ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return f'{self.document} {self.line}:{self.column}' | ||||
|  | ||||
|     @property | ||||
|     def as_line_directive(self): | ||||
|         return f'#line {self.line + 1} "{self.document}"' | ||||
|  | ||||
|  | ||||
| class DocumentRange: | ||||
|     def __init__(self, start_mark: DocumentLocation, end_mark: DocumentLocation): | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import abc | ||||
| import inspect | ||||
| import math | ||||
| import re | ||||
|  | ||||
| # pylint: disable=unused-import, wrong-import-order | ||||
| from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence | ||||
| @@ -188,13 +189,14 @@ class ParameterListExpression(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 | ||||
|         if not isinstance(parameters, ParameterListExpression): | ||||
|             parameters = ParameterListExpression(*parameters) | ||||
|         self.parameters = parameters | ||||
|         self.source = source | ||||
|         self.capture = capture | ||||
|         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})' | ||||
|         if self.return_type is not None: | ||||
|             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) | ||||
|  | ||||
|     @property | ||||
| @@ -360,7 +365,7 @@ class LineComment(Statement): | ||||
|         self.value = value | ||||
|  | ||||
|     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] | ||||
|         return '\n'.join(parts) | ||||
|  | ||||
| @@ -555,7 +560,7 @@ def process_lambda( | ||||
|         else: | ||||
|             parts[i * 3 + 1] = var | ||||
|         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): | ||||
|   | ||||
| @@ -11,7 +11,8 @@ import yaml.constructor | ||||
|  | ||||
| from esphome import core | ||||
| 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.util import OrderedDict, filter_yaml_files | ||||
|  | ||||
| @@ -30,9 +31,16 @@ class ESPHomeDataBase: | ||||
|     def esp_range(self): | ||||
|         return getattr(self, '_esp_range', None) | ||||
|  | ||||
|     @property | ||||
|     def content_offset(self): | ||||
|         return getattr(self, '_content_offset', 0) | ||||
|  | ||||
|     def from_node(self, node): | ||||
|         # pylint: disable=attribute-defined-outside-init | ||||
|         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: | ||||
| @@ -257,7 +265,10 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | ||||
|  | ||||
|     @_add_data_ref | ||||
|     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 | ||||
|     def construct_force(self, node): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user