mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 16:37:52 +01: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:
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):
|
||||||
|
Loading…
Reference in New Issue
Block a user