-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ca272a8
commit a5e78d9
Showing
2 changed files
with
291 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,3 @@ | ||
# Downloaded from `make dependencies` | ||
slpp.py | ||
|
||
# PyCharm & VSCodium | ||
.idea | ||
.vscode | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
import re | ||
import sys | ||
from numbers import Number | ||
|
||
import six | ||
|
||
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, six.string_types): | ||
return | ||
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 six.PY2 and isinstance(obj, unicode): | ||
s += '"%s"' % obj.encode('utf-8').replace(r'"', r'\"') | ||
elif six.PY3 and 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, six.string_types) 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_list = ['[%s]' if isinstance(k, Number) else '["%s"]' for k in obj.keys()] | ||
contents = [dp + (key + ' = %s') % (k, self.__encode(v)) for (k, v), key in zip(obj.items(), key_list)] | ||
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 | ||
self.comment() | ||
|
||
def comment(self): | ||
if self.ch == '-' and self.next_is('-'): | ||
self.next_chr() | ||
# TODO: for fancy comments need to improve | ||
multiline = self.next_chr() and self.ch == '[' and self.next_is('[') | ||
while self.ch: | ||
if multiline: | ||
if self.ch == ']' and self.next_is(']'): | ||
self.next_chr() | ||
self.next_chr() | ||
self.white() | ||
break | ||
# `--` is a comment, skip to next new line | ||
elif re.match('\n', self.ch): | ||
self.white() | ||
break | ||
self.next_chr() | ||
|
||
def next_is(self, value): | ||
if self.at >= self.len: | ||
return False | ||
return self.text[self.at] == value | ||
|
||
def prev_is(self, value): | ||
if self.at < 2: | ||
return False | ||
return self.text[self.at-2] == value | ||
|
||
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 ['"', "'", '[']: | ||
double = start=='[' and self.prev_is(start) | ||
while self.next_chr(): | ||
if self.ch == end and (not double or self.next_is(end)): | ||
self.next_chr() | ||
if start != "[" or self.ch == ']': | ||
if double: | ||
self.next_chr() | ||
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, six.string_types + (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'] |