clex-py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CLEX/__init__.py +0 -0
- CLEX/engine.py +42 -0
- CLEX/parser.py +139 -0
- clex_py-0.1.0.dist-info/METADATA +12 -0
- clex_py-0.1.0.dist-info/RECORD +8 -0
- clex_py-0.1.0.dist-info/WHEEL +5 -0
- clex_py-0.1.0.dist-info/licenses/LICENSE +0 -0
- clex_py-0.1.0.dist-info/top_level.txt +1 -0
CLEX/__init__.py
ADDED
|
File without changes
|
CLEX/engine.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class Engine:
|
|
2
|
+
def __init__(self, AST):
|
|
3
|
+
self.AST = AST
|
|
4
|
+
|
|
5
|
+
def __call__(self, *inputs):
|
|
6
|
+
return self.eval(inputs)
|
|
7
|
+
|
|
8
|
+
def eval(self, **inputs):
|
|
9
|
+
actions = {
|
|
10
|
+
'ADD': lambda x, y: [a + b for a, b in zip(x, y)],
|
|
11
|
+
'SUB': lambda x, y: [a - b for a, b in zip(x, y)],
|
|
12
|
+
'MUL': lambda x, y: [a * b for a, b in zip(x, y)],
|
|
13
|
+
'DIV': lambda x, y: [a / b for a, b in zip(x, y)],
|
|
14
|
+
'MOD': lambda x, y: [a % b for a, b in zip(x, y)],
|
|
15
|
+
|
|
16
|
+
'EQ': lambda x, y: [a == b for a, b in zip(x, y)],
|
|
17
|
+
'GT': lambda x, y: [a > b for a, b in zip(x, y)],
|
|
18
|
+
'LT': lambda x, y: [a < b for a, b in zip(x, y)],
|
|
19
|
+
'GE': lambda x, y: [a >= b for a, b in zip(x, y)],
|
|
20
|
+
'LE': lambda x, y: [a <= b for a, b in zip(x, y)],
|
|
21
|
+
'NE': lambda x, y: [a != b for a, b in zip(x, y)],
|
|
22
|
+
|
|
23
|
+
'IDX': lambda x, y: list(x[y]),
|
|
24
|
+
'PARSE': lambda x, y: [element for element, condition in zip(x, y) if condition],
|
|
25
|
+
'NPARSE': lambda x, y: [element for element, condition in zip(x, y) if not condition],
|
|
26
|
+
'IN': lambda x, y: list(set(x) & set(y)),
|
|
27
|
+
'NIN': lambda x, y: list(set(x) - set(y))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_inputs = {**inputs}
|
|
31
|
+
_val_buffer = list()
|
|
32
|
+
|
|
33
|
+
for func in self.AST:
|
|
34
|
+
op, v1, v2, ret = func
|
|
35
|
+
input = _inputs[v1[1]]
|
|
36
|
+
is_v2_input = (v2[0] == 'INPUT')
|
|
37
|
+
v2_data = _inputs[v2[1]] if is_v2_input else [v2[1]] * len(input)
|
|
38
|
+
|
|
39
|
+
_val_buffer = actions[op](input, v2_data)
|
|
40
|
+
_inputs[ret] = _val_buffer
|
|
41
|
+
|
|
42
|
+
return _val_buffer
|
CLEX/parser.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# (x + y; r)
|
|
2
|
+
# () define the function
|
|
3
|
+
# letters define inputs or values to be processed
|
|
4
|
+
# a function is composed of 1+inputs, an operation a value, and a return value
|
|
5
|
+
# (input1 op constant or input)
|
|
6
|
+
# @ could retrieve the element at index v2 e.g: (x @ 2:)
|
|
7
|
+
|
|
8
|
+
# {} could define the use of outside functions exposed to the engine
|
|
9
|
+
|
|
10
|
+
from string import digits, ascii_letters, punctuation
|
|
11
|
+
from engine import Engine
|
|
12
|
+
|
|
13
|
+
class ParserError(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def expression(functions):
|
|
18
|
+
parser = {
|
|
19
|
+
"split_values": [c for c in ascii_letters + digits],
|
|
20
|
+
"funcs": _get_functions(functions),
|
|
21
|
+
"cursor": 0,
|
|
22
|
+
"AST": [],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
while _should_parse(parser):
|
|
26
|
+
_match(parser, '(')
|
|
27
|
+
parser["AST"].append(_parse_expression(parser))
|
|
28
|
+
_match(parser, ')')
|
|
29
|
+
|
|
30
|
+
return Engine(parser["AST"]).eval
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _parse_value_or_input(parser):
|
|
34
|
+
if _current(parser) in ascii_letters:
|
|
35
|
+
var_name = _match(parser, parser["split_values"])
|
|
36
|
+
return ('INPUT', var_name)
|
|
37
|
+
|
|
38
|
+
val = _match(parser, digits)
|
|
39
|
+
return ('VALUE', int(val))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _parse_return(parser):
|
|
43
|
+
match _current(parser):
|
|
44
|
+
case ':':
|
|
45
|
+
_next(parser)
|
|
46
|
+
return '_'
|
|
47
|
+
case ';':
|
|
48
|
+
_next(parser)
|
|
49
|
+
return _match(parser, ascii_letters)
|
|
50
|
+
case _:
|
|
51
|
+
raise ParserError(f"Expected ';' or ':' instead got {_current(parser)}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _parse_expression(parser):
|
|
55
|
+
OP_MAP = {
|
|
56
|
+
'+': 'ADD',
|
|
57
|
+
'-': 'SUB',
|
|
58
|
+
'*': 'MUL',
|
|
59
|
+
'/': 'DIV',
|
|
60
|
+
'%': 'MOD',
|
|
61
|
+
'>': 'GT',
|
|
62
|
+
'<': 'LT',
|
|
63
|
+
'>=': 'GE',
|
|
64
|
+
'<=': 'LE',
|
|
65
|
+
'=': 'EQ',
|
|
66
|
+
'!=': 'NE',
|
|
67
|
+
'@': 'IDX',
|
|
68
|
+
'&': 'PARSE',
|
|
69
|
+
'!&': 'NPARSE',
|
|
70
|
+
'^': 'IN',
|
|
71
|
+
'!^': 'NIN',
|
|
72
|
+
}
|
|
73
|
+
sorted_ops = sorted(OP_MAP.keys(), key=len, reverse=True)
|
|
74
|
+
|
|
75
|
+
v1 = _parse_value_or_input(parser)
|
|
76
|
+
op = OP_MAP[_match(parser, sorted_ops)]
|
|
77
|
+
v2 = _parse_value_or_input(parser)
|
|
78
|
+
ret = _parse_return(parser)
|
|
79
|
+
|
|
80
|
+
return (op, v1, v2, ret)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _should_parse(parser):
|
|
84
|
+
return parser["cursor"] < len(parser["funcs"])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _next(parser):
|
|
88
|
+
parser["cursor"] += 1
|
|
89
|
+
return _current(parser)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _current(parser) -> str:
|
|
93
|
+
if parser["cursor"] >= len(parser["funcs"]):
|
|
94
|
+
return ""
|
|
95
|
+
return parser["funcs"][parser["cursor"]]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _match(parser, *expected):
|
|
99
|
+
expected_set = {i for e in expected for i in (e if isinstance(e, (list, tuple, str)) else [e])}
|
|
100
|
+
|
|
101
|
+
current_token = _current(parser)
|
|
102
|
+
if current_token in expected_set:
|
|
103
|
+
_next(parser)
|
|
104
|
+
return current_token
|
|
105
|
+
|
|
106
|
+
raise ParserError(f"Expected {expected} instead got {current_token}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_functions(expr_input: str):
|
|
110
|
+
funcs = expr_input.split('->')
|
|
111
|
+
tokens = []
|
|
112
|
+
|
|
113
|
+
valid_operators = {"+", "-", "*", "/", "%", ">", "<", '^', '!^', ">=", "<=", "=", "!=", "@", "&", "!&"}
|
|
114
|
+
valid_prefixes = {op[:i] for op in valid_operators for i in range(1, len(op) + 1)}
|
|
115
|
+
|
|
116
|
+
for func in funcs:
|
|
117
|
+
buffer = ""
|
|
118
|
+
for c in func:
|
|
119
|
+
if not c.strip():
|
|
120
|
+
if buffer:
|
|
121
|
+
tokens.append(buffer)
|
|
122
|
+
buffer = ""
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
if c in punctuation:
|
|
126
|
+
if buffer and (buffer + c) not in valid_prefixes:
|
|
127
|
+
tokens.append(buffer)
|
|
128
|
+
buffer = ""
|
|
129
|
+
buffer += c
|
|
130
|
+
else:
|
|
131
|
+
if buffer:
|
|
132
|
+
tokens.append(buffer)
|
|
133
|
+
buffer = ""
|
|
134
|
+
tokens.append(c)
|
|
135
|
+
|
|
136
|
+
if buffer:
|
|
137
|
+
tokens.append(buffer)
|
|
138
|
+
|
|
139
|
+
return tokens
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clex-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An alternative to functional programming
|
|
5
|
+
Author-email: cfarr2019@gmail.com
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
CLEX/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
CLEX/engine.py,sha256=L8ish2QBpgy_tIHkkgxasuKS4MPicKlpdNACsiTnRPA,1717
|
|
3
|
+
CLEX/parser.py,sha256=uEv7lzy1hJPGHhSMxJKwArSMJvGd_Vi3zk1Akf-V1ok,3712
|
|
4
|
+
clex_py-0.1.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
clex_py-0.1.0.dist-info/METADATA,sha256=jn10kVyHRj6Xs90DRd72LPqlDIXydDG0mnu4flDFdco,400
|
|
6
|
+
clex_py-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
clex_py-0.1.0.dist-info/top_level.txt,sha256=YcwZXsh50oH5CgUh5H1KnDDwlMKAzR1mW9yVFEL4M0E,5
|
|
8
|
+
clex_py-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CLEX
|