wow-addons/z-BigBrother/bb/slpp.py

282 lines
9 KiB
Python

"""
Simple lua-python data structures parser from https://github.com/SirAnthony/slpp (b947496 @ 28-04-2020).
Copyright (c) 2010, 2011, 2012 SirAnthony <anthony at adsorbtion.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import re
import sys
from numbers import Number
ERRORS = {
'unexp_end_string': u'Unexpected end of string while parsing Lua string.',
'unexp_end_table': u'Unexpected end of table while parsing Lua string.',
'mfnumber_minus': u'Malformed number (no digits after initial minus).',
'mfnumber_dec_point': u'Malformed number (no digits after decimal point).',
'mfnumber_sci': u'Malformed number (bad scientific format).',
}
def sequential(lst):
length = len(lst)
if length == 0 or lst[0] != 0:
return False
for i in range(length):
if i + 1 < length:
if lst[i] + 1 != lst[i+1]:
return False
return True
class ParseError(Exception):
pass
class SLPP(object):
def __init__(self):
self.text = ''
self.ch = ''
self.at = 0
self.len = 0
self.depth = 0
self.space = re.compile('\s', re.M)
self.alnum = re.compile('\w', re.M)
self.newline = '\n'
self.tab = '\t'
def decode(self, text):
if not text or not isinstance(text, str):
return
# FIXME: only short comments removed
reg = re.compile('--.*$', re.M)
text = reg.sub('', text, 0)
self.text = text
self.at, self.ch, self.depth = 0, '', 0
self.len = len(text)
self.next_chr()
result = self.value()
return result
def encode(self, obj):
self.depth = 0
return self.__encode(obj)
def __encode(self, obj):
s = ''
tab = self.tab
newline = self.newline
if isinstance(obj, str):
s += '"%s"' % obj.replace(r'"', r'\"')
elif isinstance(obj, bytes):
s += '"{}"'.format(''.join(r'\x{:02x}'.format(c) for c in obj))
elif isinstance(obj, bool):
s += str(obj).lower()
elif obj is None:
s += 'nil'
elif isinstance(obj, Number):
s += str(obj)
elif isinstance(obj, (list, tuple, dict)):
self.depth += 1
if len(obj) == 0 or (not isinstance(obj, dict) and len([
x for x in obj
if isinstance(x, Number) or (isinstance(x, str) and len(x) < 10)
]) == len(obj)):
newline = tab = ''
dp = tab * self.depth
s += "%s{%s" % (tab * (self.depth - 2), newline)
if isinstance(obj, dict):
key = '[%s]' if all(isinstance(k, int) for k in obj.keys()) else '%s'
contents = [dp + (key + ' = %s') % (k, self.__encode(v)) for k, v in obj.items()]
s += (',%s' % newline).join(contents)
else:
s += (',%s' % newline).join(
[dp + self.__encode(el) for el in obj])
self.depth -= 1
s += "%s%s}" % (newline, tab * self.depth)
return s
def white(self):
while self.ch:
if self.space.match(self.ch):
self.next_chr()
else:
break
def next_chr(self):
if self.at >= self.len:
self.ch = None
return None
self.ch = self.text[self.at]
self.at += 1
return True
def value(self):
self.white()
if not self.ch:
return
if self.ch == '{':
return self.object()
if self.ch == "[":
self.next_chr()
if self.ch in ['"', "'", '[']:
return self.string(self.ch)
if self.ch.isdigit() or self.ch == '-':
return self.number()
return self.word()
def string(self, end=None):
s = ''
start = self.ch
if end == '[':
end = ']'
if start in ['"', "'", '[']:
while self.next_chr():
if self.ch == end:
self.next_chr()
if start != "[" or self.ch == ']':
return s
if self.ch == '\\' and start == end:
self.next_chr()
if self.ch != end:
s += '\\'
s += self.ch
raise ParseError(ERRORS['unexp_end_string'])
def object(self):
o = {}
k = None
idx = 0
numeric_keys = False
self.depth += 1
self.next_chr()
self.white()
if self.ch and self.ch == '}':
self.depth -= 1
self.next_chr()
return o # Exit here
else:
while self.ch:
self.white()
if self.ch == '{':
o[idx] = self.object()
idx += 1
continue
elif self.ch == '}':
self.depth -= 1
self.next_chr()
if k is not None:
o[idx] = k
if len([key for key in o if isinstance(key, (str, float, bool, tuple))]) == 0:
so = sorted([key for key in o])
if sequential(so):
ar = []
for key in o:
ar.insert(key, o[key])
o = ar
return o # or here
else:
if self.ch == ',':
self.next_chr()
continue
else:
k = self.value()
if self.ch == ']':
self.next_chr()
self.white()
ch = self.ch
if ch in ('=', ','):
self.next_chr()
self.white()
if ch == '=':
o[k] = self.value()
else:
o[idx] = k
idx += 1
k = None
raise ParseError(ERRORS['unexp_end_table']) # Bad exit here
words = {'true': True, 'false': False, 'nil': None}
def word(self):
s = ''
if self.ch != '\n':
s = self.ch
self.next_chr()
while self.ch is not None and self.alnum.match(self.ch) and s not in self.words:
s += self.ch
self.next_chr()
return self.words.get(s, s)
def number(self):
def next_digit(err):
n = self.ch
self.next_chr()
if not self.ch or not self.ch.isdigit():
raise ParseError(err)
return n
n = ''
try:
if self.ch == '-':
n += next_digit(ERRORS['mfnumber_minus'])
n += self.digit()
if n == '0' and self.ch in ['x', 'X']:
n += self.ch
self.next_chr()
n += self.hex()
else:
if self.ch and self.ch == '.':
n += next_digit(ERRORS['mfnumber_dec_point'])
n += self.digit()
if self.ch and self.ch in ['e', 'E']:
n += self.ch
self.next_chr()
if not self.ch or self.ch not in ('+', '-'):
raise ParseError(ERRORS['mfnumber_sci'])
n += next_digit(ERRORS['mfnumber_sci'])
n += self.digit()
except ParseError:
t, e = sys.exc_info()[:2]
print(e)
return 0
try:
return int(n, 0)
except:
pass
return float(n)
def digit(self):
n = ''
while self.ch and self.ch.isdigit():
n += self.ch
self.next_chr()
return n
def hex(self):
n = ''
while self.ch and (self.ch in 'ABCDEFabcdef' or self.ch.isdigit()):
n += self.ch
self.next_chr()
return n
slpp = SLPP()
__all__ = ['slpp']