Skip to content

Commit

Permalink
Add the BYTES type definition and literal parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroSteiner committed Jun 8, 2024
1 parent 548a6bd commit 7ea8906
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 5 deletions.
6 changes: 6 additions & 0 deletions docs/source/rule_engine/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ Literal Expressions
.. autoattribute:: result_type
:annotation: = BOOLEAN

.. autoclass:: BytesExpression
:show-inheritance:

.. autoattribute:: result_type
:annotation: = BYTES

.. autoclass:: DatetimeExpression
:show-inheritance:

Expand Down
2 changes: 2 additions & 0 deletions docs/source/rule_engine/parser/utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This module contains utility functions for parsing various strings.
Functions
---------

.. autofunction:: parse_bytes

.. autofunction:: parse_datetime

.. autofunction:: parse_float
Expand Down
3 changes: 3 additions & 0 deletions docs/source/rule_engine/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Classes
.. autoattribute:: BOOLEAN
:annotation:

.. autoattribute:: BYTES
:annotation:

.. autoattribute:: DATETIME
:annotation:

Expand Down
11 changes: 10 additions & 1 deletion lib/rule_engine/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import re

from . import errors
from .parser.utilities import parse_datetime, parse_float, parse_timedelta
from .parser.utilities import parse_bytes, parse_datetime, parse_float, parse_timedelta
from .suggestions import suggest_symbol
from .types import *

Expand Down Expand Up @@ -232,6 +232,15 @@ class BooleanExpression(LiteralExpressionBase):
"""Literal boolean expressions representing True or False."""
result_type = DataType.BOOLEAN

class BytesExpression(LiteralExpressionBase):
"""
Literal bytes expressions representing a binary string. This expression type always evaluates to true when not empty.
"""
result_type = DataType.BYTES
@classmethod
def from_string(cls, context, string):
return cls(context, parse_bytes(string))

class DatetimeExpression(LiteralExpressionBase):
"""
Literal datetime expressions representing a specific point in time. This expression type always evaluates to true.
Expand Down
15 changes: 12 additions & 3 deletions lib/rule_engine/parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class Parser(ParserBase):
'if': 'IF'
}
tokens = (
'DATETIME', 'TIMEDELTA', 'FLOAT', 'STRING', 'SYMBOL',
'BYTES', 'DATETIME', 'TIMEDELTA', 'FLOAT', 'STRING', 'SYMBOL',
'LPAREN', 'RPAREN', 'QMARK', 'COLON', 'COMMA',
'LBRACKET', 'RBRACKET', 'LBRACE', 'RBRACE', 'COMMENT'
) + tuple(set(list(reserved_words.values()) + list(op_names.values())))
Expand Down Expand Up @@ -209,9 +209,14 @@ def t_NE_FZS(self, t):
t.type = 'NE_FZM'
return t

def t_BYTES(self, t):
r'b(?P<quote>["\'])([^\\\n]|(\\.))*?(?P=quote)'
t.value = t.value[2:-1]
return t

def t_DATETIME(self, t):
r'd(?P<quote>["\'])([^\\\n]|(\\.))*?(?P=quote)'
t.value = t.value[1:]
t.value = t.value[2:-1]
return t

def t_TIMEDELTA(self, t):
Expand Down Expand Up @@ -408,9 +413,13 @@ def p_expression_boolean(self, p):
"""
p[0] = _DeferredAstNode(ast.BooleanExpression, args=(self.context, p[1] == 'true'))

def p_expression_bytes(self, p):
'object : BYTES'
p[0] = _DeferredAstNode(ast.BytesExpression, args=(self.context, p[1]), method='from_string')

def p_expression_datetime(self, p):
'object : DATETIME'
p[0] = _DeferredAstNode(ast.DatetimeExpression, args=(self.context, literal_eval(p[1])), method='from_string')
p[0] = _DeferredAstNode(ast.DatetimeExpression, args=(self.context, p[1]), method='from_string')

def p_expression_timedelta(self, p):
'object : TIMEDELTA'
Expand Down
20 changes: 19 additions & 1 deletion lib/rule_engine/parser/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#

import ast as pyast
import binascii
import datetime
import decimal
import re
Expand All @@ -51,6 +52,23 @@
r')?'
)

def parse_bytes(string):
"""
Parse a bytes string in ascii-hex format.
:param str string: The string to parse.
:rtype: bytes
"""
if not string:
return bytes
elif re.match(r'^(\\x[A-Fa-f0-9]{2})+$', string):
string = re.sub(r'\\x', '', string)
return binascii.a2b_hex(string)
elif re.match(r'^([A-Fa-f0-9]{2}:)*[A-Fa-f0-9]{2}$', string):
string = string.replace(':', '')
return binascii.a2b_hex(string)
raise errors.BytesSyntaxError('invalid bytes literal', string)

def parse_datetime(string, default_timezone):
"""
Parse a timestamp string. If the timestamp does not specify a timezone, *default_timezone* is used.
Expand All @@ -62,7 +80,7 @@ def parse_datetime(string, default_timezone):
try:
dt = dateutil.parser.isoparse(string)
except ValueError:
raise errors.DatetimeSyntaxError('invalid datetime', string) from None
raise errors.DatetimeSyntaxError('invalid datetime literal', string) from None
if dt.tzinfo is None:
dt = dt.replace(tzinfo=default_timezone)
return dt
Expand Down
5 changes: 5 additions & 0 deletions lib/rule_engine/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ class DataType(metaclass=DataTypeMeta):
this ensures that the member types are either the same or :py:attr:`~.UNDEFINED`.
"""
ARRAY = staticmethod(_ArrayDataTypeDef('ARRAY', tuple))
BYTES = _DataTypeDef('BYTES', bytes)
BOOLEAN = _DataTypeDef('BOOLEAN', bool)
DATETIME = _DataTypeDef('DATETIME', datetime.datetime)
FLOAT = _DataTypeDef('FLOAT', decimal.Decimal)
Expand Down Expand Up @@ -483,6 +484,8 @@ def from_type(cls, python_type):
return cls.ARRAY
elif python_type is bool:
return cls.BOOLEAN
elif python_type is bytes:
return cls.BYTES
elif python_type is datetime.date or python_type is datetime.datetime:
return cls.DATETIME
elif python_type is datetime.timedelta:
Expand Down Expand Up @@ -526,6 +529,8 @@ def from_value(cls, python_value):
"""
if isinstance(python_value, bool):
return cls.BOOLEAN
elif isinstance(python_value, bytes):
return cls.BYTES
elif isinstance(python_value, (datetime.date, datetime.datetime)):
return cls.DATETIME
elif isinstance(python_value, datetime.timedelta):
Expand Down

0 comments on commit 7ea8906

Please sign in to comment.