- change secop.client.SecopClient to use native types instead of strings for its setParameter and execCommand methods. - secop-gui: for now, setParameter accept strings for complex types. this should be changed to use native type in an other change - fix bugs in parser.py + SecopClient: make visible in an error message that the error was generated on the SEC node + fix a bug when a command is called with 0 as argument Change-Id: Id87d4678311ef8cf43a25153254d36127e16c6d9 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/23299 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
178 lines
5.7 KiB
Python
178 lines
5.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
# *****************************************************************************
|
||
#
|
||
# This program is free software; you can redistribute it and/or modify it under
|
||
# the terms of the GNU General Public License as published by the Free Software
|
||
# Foundation; either version 2 of the License, or (at your option) any later
|
||
# version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||
# details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License along with
|
||
# this program; if not, write to the Free Software Foundation, Inc.,
|
||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
#
|
||
# Module authors:
|
||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||
#
|
||
# *****************************************************************************
|
||
"""parser. used for config files and the gui
|
||
|
||
can't use ast.literal_eval as we want less strict syntax (strings without quotes)
|
||
|
||
parsing rules:
|
||
(...) -> tuple
|
||
[...] -> tuple
|
||
{text:...} -> dict
|
||
{text=...} -> dict
|
||
..., ... -> tuple
|
||
digits -> float or int (try int, if it fails: take float)
|
||
text -> string
|
||
'text' -> string
|
||
"text" -> string
|
||
|
||
further conversions are done by the validator of the datatype....
|
||
"""
|
||
|
||
# TODO: should be refactored to use Exceptions instead of None in return tuple
|
||
# also it would be better to use functions instead of a class
|
||
|
||
|
||
from collections import OrderedDict
|
||
|
||
|
||
class Parser:
|
||
# all parsing methods return (parsed value, remaining string)
|
||
# or (None, remaining_text) if parsing error
|
||
|
||
def parse_number(self, text):
|
||
text = text.strip()
|
||
l = 1
|
||
number = None
|
||
while l <= len(text):
|
||
try:
|
||
number = float(text[:l])
|
||
length = l
|
||
l += 1
|
||
except ValueError:
|
||
if text[l - 1] in 'eE+-':
|
||
l += 1
|
||
continue
|
||
if number is None:
|
||
return None, text
|
||
try:
|
||
return int(text[:length]), text[length:]
|
||
except ValueError:
|
||
return number, text[length:]
|
||
return number, ''
|
||
|
||
def parse_string(self, orgtext):
|
||
# handle quoted and unquoted strings correctly
|
||
text = orgtext.strip()
|
||
if text[0] in ('"', "'"):
|
||
# quoted string
|
||
quote = text[0]
|
||
idx = 0
|
||
|
||
while True:
|
||
idx = text.find(quote, idx + 1)
|
||
if idx == -1:
|
||
return None, orgtext
|
||
# check escapes!
|
||
if text[idx - 1] == '\\':
|
||
continue
|
||
return text[1:idx], text[idx + 1:].strip()
|
||
|
||
# unquoted strings are terminated by comma or whitespace
|
||
idx = 0
|
||
while idx < len(text):
|
||
if text[idx] in '\x09 ,.;:()[]{}<>-+*/\\!"§$%&=?#~+*\'´`^°|-':
|
||
break
|
||
idx += 1
|
||
return text[:idx] or None, text[idx:].strip()
|
||
|
||
def parse_tuple(self, orgtext):
|
||
text = orgtext.strip()
|
||
bra = text[0]
|
||
if bra not in '([<':
|
||
return None, orgtext
|
||
# convert to closing bracket
|
||
bra = ')]>'['([<'.index(bra)]
|
||
reslist = []
|
||
# search for closing bracket, collecting results
|
||
text = text[1:]
|
||
while text:
|
||
if bra not in text:
|
||
return None, text
|
||
res, rem = self.parse_sub(text)
|
||
if res is None:
|
||
print('remtuple %r %r %r' % (rem, text, bra))
|
||
if rem[0] == bra:
|
||
# allow trailing separator
|
||
return tuple(reslist), rem[1:].strip()
|
||
return None, text
|
||
reslist.append(res)
|
||
if rem[0] == bra:
|
||
return tuple(reslist), rem[1:].strip()
|
||
# eat separator
|
||
if rem[0] in ',;':
|
||
text = rem[1:]
|
||
else:
|
||
return None, rem
|
||
return None, orgtext
|
||
|
||
def parse_dict(self, orgtext):
|
||
text = orgtext.strip()
|
||
if text[0] != '{':
|
||
return None, orgtext
|
||
# keep ordering
|
||
result = OrderedDict()
|
||
# search for closing bracket, collecting results
|
||
# watch for key=value or key:value pairs, separated by ,
|
||
text = text[1:]
|
||
while '}' in text:
|
||
# first part is always a string
|
||
key, rem = self.parse_string(text)
|
||
if key is None:
|
||
if rem[0] == '}':
|
||
# allow trailing separator
|
||
return result, rem[1:].strip()
|
||
return None, orgtext
|
||
if rem[0] not in ':=':
|
||
return None, rem
|
||
# eat separator
|
||
text = rem[1:]
|
||
value, rem = self.parse_sub(text)
|
||
if value is None:
|
||
return None, orgtext
|
||
result[key] = value
|
||
if rem[0] == '}':
|
||
return result, rem[1:].strip()
|
||
|
||
if rem[0] not in ',;':
|
||
return None, rem
|
||
# eat separator
|
||
text = rem[1:]
|
||
return None, text
|
||
|
||
def parse_sub(self, orgtext):
|
||
text = orgtext.strip()
|
||
if not text:
|
||
return None, orgtext
|
||
if text[0] in '+-.0123456789':
|
||
return self.parse_number(orgtext)
|
||
if text[0] == '{':
|
||
return self.parse_dict(orgtext)
|
||
if text[0] in '([<':
|
||
return self.parse_tuple(orgtext)
|
||
return self.parse_string(orgtext)
|
||
|
||
def parse(self, orgtext):
|
||
res, rem = self.parse_sub(orgtext)
|
||
if rem and rem[0] in ',;':
|
||
return self.parse_sub('[%s]' % orgtext)
|
||
return res, rem
|