Skip to content

Commit 252d5f3

Browse files
authored
Move the Gaeval parser to its own file (#304)
Whether this belongs in the `galgebra.printer` namespace at all is a different question, to be addressed another time.
1 parent a277aca commit 252d5f3

File tree

2 files changed

+174
-165
lines changed

2 files changed

+174
-165
lines changed

galgebra/_utils/parser.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
""" A private module to parse multivector expressions """
2+
import re
3+
4+
op_cntrct = re.compile(r'(([A-Za-z0-9\_\#]+)(\||<|>)([A-Za-z0-9\_\#]+))')
5+
op_wedge = re.compile(r'(([A-Za-z0-9\_\#]+)[\^]{1}([A-Za-z0-9\_\#]+)([\^]{1}([A-Za-z0-9\_\#]+))*)')
6+
ops = r'[\^\|\<\>]+'
7+
ops_search = re.compile(r'(\^|\||<|>)+')
8+
parse_paren_calls = 0
9+
op_dict = {}
10+
op_lst = []
11+
12+
OPS = {'<>|': r'(([A-Za-z0-9\_\#]+)(\||<|>)([A-Za-z0-9\_\#]+))',
13+
'^': r'(([A-Za-z0-9\_\#]+)[\^]{1}([A-Za-z0-9\_\#]+)([\^]{1}([A-Za-z0-9\_\#]+))*)',
14+
'*': r'(([A-Za-z0-9\_\#]+)[\*]{1}([A-Za-z0-9\_\#]+)([\*]{1}([A-Za-z0-9\_\#]+))*)'}
15+
16+
17+
def set_precedence(op_ord: str = '<>|,^,*') -> None:
18+
global op_dict, op_lst
19+
op_lst = op_ord.split(',')
20+
op_dict = {}
21+
for op in op_lst:
22+
op_dict[op] = re.compile(OPS[op])
23+
24+
25+
def _contains_interval(interval1, interval2): # interval1 inside interval2
26+
if interval1[0] > interval2[0] and interval1[1] < interval2[1]:
27+
return True
28+
else:
29+
return False
30+
31+
32+
def _parse_paren(line):
33+
global parse_paren_calls
34+
parse_paren_calls += 1
35+
36+
if ('(' not in line) or (')' not in line):
37+
return [[[line]]]
38+
level = 0
39+
max_level = 0
40+
ich = 0
41+
paren_lst = []
42+
for ch in line:
43+
if ch == '(':
44+
level += 1
45+
paren_lst.append([level, ich])
46+
if ch == ')':
47+
if level < 1:
48+
raise ValueError('Mismathed Parenthesis in: ' + line + '\n')
49+
paren_lst.reverse()
50+
iparen = 0
51+
for elem in paren_lst:
52+
if elem[0] == level:
53+
paren_lst[iparen].append(ich)
54+
break
55+
iparen += 1
56+
paren_lst.reverse()
57+
level -= 1
58+
max_level = max(max_level, level)
59+
ich += 1
60+
if level != 0:
61+
raise ValueError('Mismatched Parenthesis in: ' + line + '\n')
62+
if max_level > 0:
63+
level_lst = []
64+
for _x in range(max_level + 1):
65+
level_lst.append([])
66+
for group in paren_lst:
67+
level_lst[group[0]].append(group[1:])
68+
ilevel = max_level
69+
while ilevel > 1:
70+
level = level_lst[ilevel]
71+
level_down = level_lst[ilevel - 1]
72+
igroup = 0
73+
for group in level:
74+
igroup_down = 0
75+
for group_down in level_down:
76+
if _contains_interval(group, group_down):
77+
level_lst[ilevel][igroup].append(igroup_down)
78+
igroup_down += 1
79+
igroup += 1
80+
ilevel -= 1
81+
ilevel = 1
82+
for level in level_lst[1:]:
83+
igroup = 0
84+
for group in level:
85+
token = '#' + str(parse_paren_calls) + '_' + str(ilevel) + '_' + str(igroup) + '#'
86+
level_lst[ilevel][igroup].append(line[group[0]:group[1] + 1])
87+
level_lst[ilevel][igroup].append(token)
88+
igroup += 1
89+
ilevel += 1
90+
ilevel = 1
91+
for level in level_lst[1:]:
92+
igroup = 0
93+
for group in level:
94+
group.append(group[-2])
95+
level_lst[ilevel][igroup] = group
96+
igroup += 1
97+
ilevel += 1
98+
ilevel = max_level
99+
while ilevel > 1:
100+
igroup = 0
101+
for group in level_lst[ilevel]:
102+
group_down = level_lst[ilevel - 1][group[2]]
103+
replace_text = group_down[-1].replace(group[-3], group[-2])
104+
level_lst[ilevel - 1][group[2]][-1] = replace_text
105+
igroup += 1
106+
ilevel -= 1
107+
for group in level_lst[1]:
108+
line = line.replace(group[2], group[3])
109+
ilevel = 1
110+
level_lst[0] = [[line]]
111+
return level_lst
112+
113+
114+
def _unparse_paren(level_lst):
115+
line = level_lst[0][0][0]
116+
for level in level_lst[1:]:
117+
for group in level:
118+
new_string = group[-1]
119+
if new_string[:2] == '((' and new_string[-2:] == '))':
120+
new_string = new_string[1:-1]
121+
line = line.replace(group[-2], new_string)
122+
return line
123+
124+
125+
def _sub_paren(s):
126+
string = s.group(0)
127+
return '(%s)' % string
128+
129+
130+
def _add_paren(line, re_exprs):
131+
paren_flg = False
132+
if (line[0] == '(') and (line[-1] == ')'):
133+
paren_flg = True
134+
line = line[1:-1]
135+
if ('(' in line) or (')' in line):
136+
line_levels = _parse_paren(line)
137+
ilevel = 0
138+
for level in line_levels:
139+
igroup = 0
140+
for group in level:
141+
group[-1] = re.sub(re_exprs, _sub_paren, group[-1])
142+
line_levels[ilevel][igroup] = group
143+
igroup += 1
144+
ilevel += 1
145+
line = _unparse_paren(line_levels)
146+
else:
147+
line = re.sub(re_exprs, _sub_paren, line)
148+
if paren_flg:
149+
line = '(' + line + ')'
150+
return line
151+
152+
153+
def parse_line(line: str) -> str:
154+
global op_lst, op_dict
155+
line = line.replace(' ', '')
156+
level_lst = _parse_paren(line)
157+
ilevel = 0
158+
for level in level_lst:
159+
igroup = 0
160+
for group in level:
161+
string = group[-1]
162+
for op in op_lst:
163+
string = _add_paren(string, op_dict[op])
164+
level_lst[ilevel][igroup][-1] = string
165+
igroup += 1
166+
ilevel += 1
167+
line = _unparse_paren(level_lst)
168+
return line

galgebra/printer.py

Lines changed: 6 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
import sys
77
import io
8-
import re
98
import builtins
109
import functools
1110
from sympy import Matrix, Basic, S, Symbol, Function, Derivative, Pow
@@ -27,6 +26,8 @@
2726

2827
from inspect import getouterframes, currentframe
2928

29+
from ._utils import parser as _parser
30+
3031
ZERO_STR = ' 0 '
3132

3233
Format_cnt = 0
@@ -1181,18 +1182,7 @@ def Print_Function():
11811182
return
11821183

11831184

1184-
op_cntrct = re.compile(r'(([A-Za-z0-9\_\#]+)(\||<|>)([A-Za-z0-9\_\#]+))')
1185-
op_wedge = re.compile(r'(([A-Za-z0-9\_\#]+)[\^]{1}([A-Za-z0-9\_\#]+)([\^]{1}([A-Za-z0-9\_\#]+))*)')
1186-
ops = r'[\^\|\<\>]+'
1187-
ops_search = re.compile(r'(\^|\||<|>)+')
1188-
parse_paren_calls = 0
11891185
global_dict = {}
1190-
op_dict = {}
1191-
op_lst = []
1192-
1193-
OPS = {'<>|': r'(([A-Za-z0-9\_\#]+)(\||<|>)([A-Za-z0-9\_\#]+))',
1194-
'^': r'(([A-Za-z0-9\_\#]+)[\^]{1}([A-Za-z0-9\_\#]+)([\^]{1}([A-Za-z0-9\_\#]+))*)',
1195-
'*': r'(([A-Za-z0-9\_\#]+)[\*]{1}([A-Za-z0-9\_\#]+)([\*]{1}([A-Za-z0-9\_\#]+))*)'}
11961186

11971187

11981188
def def_prec(gd: dict, op_ord: str = '<>|,^,*') -> None:
@@ -1207,159 +1197,9 @@ def def_prec(gd: dict, op_ord: str = '<>|,^,*') -> None:
12071197
The order of operator precedence from high to low with groups of equal precedence separated by commas.
12081198
The default precedence, ``'<>|,^,*'``, is that used by Hestenes (:cite:`Hestenes`, p7, :cite:`Doran`, p38).
12091199
"""
1210-
global global_dict, op_dict, op_lst
1200+
global global_dict
12111201
global_dict = gd
1212-
op_lst = op_ord.split(',')
1213-
op_dict = {}
1214-
for op in op_lst:
1215-
op_dict[op] = re.compile(OPS[op])
1216-
return
1217-
1218-
1219-
def contains_interval(interval1, interval2): # interval1 inside interval2
1220-
if interval1[0] > interval2[0] and interval1[1] < interval2[1]:
1221-
return True
1222-
else:
1223-
return False
1224-
1225-
1226-
def parse_paren(line):
1227-
global parse_paren_calls
1228-
parse_paren_calls += 1
1229-
1230-
if ('(' not in line) or (')' not in line):
1231-
return [[[line]]]
1232-
level = 0
1233-
max_level = 0
1234-
ich = 0
1235-
paren_lst = []
1236-
for ch in line:
1237-
if ch == '(':
1238-
level += 1
1239-
paren_lst.append([level, ich])
1240-
if ch == ')':
1241-
if level < 1:
1242-
raise ValueError('Mismathed Parenthesis in: ' + line + '\n')
1243-
paren_lst.reverse()
1244-
iparen = 0
1245-
for elem in paren_lst:
1246-
if elem[0] == level:
1247-
paren_lst[iparen].append(ich)
1248-
break
1249-
iparen += 1
1250-
paren_lst.reverse()
1251-
level -= 1
1252-
max_level = max(max_level, level)
1253-
ich += 1
1254-
if level != 0:
1255-
raise ValueError('Mismatched Parenthesis in: ' + line + '\n')
1256-
if max_level > 0:
1257-
level_lst = []
1258-
for _x in range(max_level + 1):
1259-
level_lst.append([])
1260-
for group in paren_lst:
1261-
level_lst[group[0]].append(group[1:])
1262-
ilevel = max_level
1263-
while ilevel > 1:
1264-
level = level_lst[ilevel]
1265-
level_down = level_lst[ilevel - 1]
1266-
igroup = 0
1267-
for group in level:
1268-
igroup_down = 0
1269-
for group_down in level_down:
1270-
if contains_interval(group, group_down):
1271-
level_lst[ilevel][igroup].append(igroup_down)
1272-
igroup_down += 1
1273-
igroup += 1
1274-
ilevel -= 1
1275-
ilevel = 1
1276-
for level in level_lst[1:]:
1277-
igroup = 0
1278-
for group in level:
1279-
token = '#' + str(parse_paren_calls) + '_' + str(ilevel) + '_' + str(igroup) + '#'
1280-
level_lst[ilevel][igroup].append(line[group[0]:group[1] + 1])
1281-
level_lst[ilevel][igroup].append(token)
1282-
igroup += 1
1283-
ilevel += 1
1284-
ilevel = 1
1285-
for level in level_lst[1:]:
1286-
igroup = 0
1287-
for group in level:
1288-
group.append(group[-2])
1289-
level_lst[ilevel][igroup] = group
1290-
igroup += 1
1291-
ilevel += 1
1292-
ilevel = max_level
1293-
while ilevel > 1:
1294-
igroup = 0
1295-
for group in level_lst[ilevel]:
1296-
group_down = level_lst[ilevel - 1][group[2]]
1297-
replace_text = group_down[-1].replace(group[-3], group[-2])
1298-
level_lst[ilevel - 1][group[2]][-1] = replace_text
1299-
igroup += 1
1300-
ilevel -= 1
1301-
for group in level_lst[1]:
1302-
line = line.replace(group[2], group[3])
1303-
ilevel = 1
1304-
level_lst[0] = [[line]]
1305-
return level_lst
1306-
1307-
1308-
def unparse_paren(level_lst):
1309-
line = level_lst[0][0][0]
1310-
for level in level_lst[1:]:
1311-
for group in level:
1312-
new_string = group[-1]
1313-
if new_string[:2] == '((' and new_string[-2:] == '))':
1314-
new_string = new_string[1:-1]
1315-
line = line.replace(group[-2], new_string)
1316-
return line
1317-
1318-
1319-
def sub_paren(s):
1320-
string = s.group(0)
1321-
return '(%s)' % string
1322-
1323-
1324-
def add_paren(line, re_exprs):
1325-
paren_flg = False
1326-
if (line[0] == '(') and (line[-1] == ')'):
1327-
paren_flg = True
1328-
line = line[1:-1]
1329-
if ('(' in line) or (')' in line):
1330-
line_levels = parse_paren(line)
1331-
ilevel = 0
1332-
for level in line_levels:
1333-
igroup = 0
1334-
for group in level:
1335-
group[-1] = re.sub(re_exprs, sub_paren, group[-1])
1336-
line_levels[ilevel][igroup] = group
1337-
igroup += 1
1338-
ilevel += 1
1339-
line = unparse_paren(line_levels)
1340-
else:
1341-
line = re.sub(re_exprs, sub_paren, line)
1342-
if paren_flg:
1343-
line = '(' + line + ')'
1344-
return line
1345-
1346-
1347-
def parse_line(line):
1348-
global op_lst, op_dict
1349-
line = line.replace(' ', '')
1350-
level_lst = parse_paren(line)
1351-
ilevel = 0
1352-
for level in level_lst:
1353-
igroup = 0
1354-
for group in level:
1355-
string = group[-1]
1356-
for op in op_lst:
1357-
string = add_paren(string, op_dict[op])
1358-
level_lst[ilevel][igroup][-1] = string
1359-
igroup += 1
1360-
ilevel += 1
1361-
line = unparse_paren(level_lst)
1362-
return line
1202+
_parser.set_precedence(op_ord)
13631203

13641204

13651205
def GAeval(s: str, pstr: bool = False):
@@ -1379,12 +1219,13 @@ def GAeval(s: str, pstr: bool = False):
13791219
enforce operator precedence are printed.
13801220
"""
13811221

1382-
seval = parse_line(s)
1222+
seval = _parser.parse_line(s)
13831223
if pstr:
13841224
print(s)
13851225
print(seval)
13861226
return eval(seval, global_dict)
13871227

1228+
13881229
r"""
13891230
\begin{array}{c}
13901231
\left ( \begin{array}{c} F,\\ \end{array} \right . \\

0 commit comments

Comments
 (0)