Skip to content

Commit

Permalink
vendor in slpp.py
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesangus committed Dec 27, 2022
1 parent ca272a8 commit a5e78d9
Show file tree
Hide file tree
Showing 2 changed files with 291 additions and 3 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# Downloaded from `make dependencies`
slpp.py

# PyCharm & VSCodium
.idea
.vscode
Expand Down
291 changes: 291 additions & 0 deletions slpp.py
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']

0 comments on commit a5e78d9

Please sign in to comment.