mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2024-11-21 06:15:56 -07:00
462 lines
21 KiB
Python
462 lines
21 KiB
Python
# (C) Copyright 2015-2024 Sei Lisa. All rights reserved.
|
|
#
|
|
# This file is part of LSL PyOptimizer.
|
|
#
|
|
# LSL PyOptimizer 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 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# LSL PyOptimizer 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 LSL PyOptimizer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Load the builtins and function properties.
|
|
|
|
import sys, re
|
|
from lslopt.lslcommon import types, warning, Vector, Quaternion
|
|
from lslopt import lslcommon, lslfuncs
|
|
from strutil import *
|
|
|
|
def LoadLibrary(builtins = None, fndata = None):
|
|
"""Load builtins.txt and fndata.txt (or the given filenames) and return
|
|
a tuple with the events, constants and functions, each in a dict.
|
|
"""
|
|
|
|
if builtins is None:
|
|
builtins = lslcommon.DataPath + 'builtins.txt'
|
|
|
|
if fndata is None:
|
|
fndata = lslcommon.DataPath + 'fndata.txt'
|
|
|
|
events = {}
|
|
constants = {}
|
|
functions = {}
|
|
|
|
|
|
# Library read code
|
|
|
|
parse_lin_re = re.compile(str2u(
|
|
r'^\s*([a-z]+)\s+'
|
|
r'([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*('
|
|
r'[a-z]+\s+[a-zA-Z_][a-zA-Z0-9_]*'
|
|
r'(?:\s*,\s*[a-z]+\s+[a-zA-Z_][a-zA-Z0-9_]*)*'
|
|
r')?\s*\)\s*$'
|
|
r'|'
|
|
r'^\s*const\s+([a-z]+)'
|
|
r'\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*?)\s*$'
|
|
r'|'
|
|
r'^\s*(?:#.*|//.*)?$'))
|
|
parse_arg_re = re.compile(str2u(
|
|
r'^\s*([a-z]+)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*$'))
|
|
parse_fp_re = re.compile(str2u(r'^\s*(-?(?=[0-9]|\.[0-9])[0-9]*'
|
|
r'((?:\.[0-9]*)?(?:[Ee][+-]?[0-9]+)?))\s*$'))
|
|
parse_int_re = re.compile(str2u(r'^\s*(-?0x[0-9A-Fa-f]+|-?[0-9]+)\s*$'))
|
|
parse_str_re = re.compile(str2u(r'^"((?:[^"\\]|\\.)*)"$'))
|
|
|
|
f = open(builtins, 'rb')
|
|
try:
|
|
linenum = 0
|
|
try:
|
|
ubuiltins = str2u(builtins, sys.getfilesystemencoding())
|
|
except UnicodeDecodeError:
|
|
# This is just a guess at the filename encoding.
|
|
ubuiltins = str2u(builtins, 'iso-8859-15')
|
|
while True:
|
|
linenum += 1
|
|
line = f.readline()
|
|
if not line: break
|
|
if line.endswith(b'\r\n'): line = line[:-2]
|
|
if line.endswith(b'\n'): line = line[:-1]
|
|
try:
|
|
line = b2u(line, 'utf8')
|
|
except UnicodeDecodeError:
|
|
warning(u"Bad Unicode in %s line %d" % (ubuiltins, linenum))
|
|
continue
|
|
match = parse_lin_re.search(line)
|
|
if not match:
|
|
warning(u"Syntax error in %s, line %d" % (ubuiltins, linenum))
|
|
continue
|
|
if match.group(1):
|
|
# event or function
|
|
typ = match.group(1)
|
|
if typ == u'quaternion':
|
|
typ = u'rotation'
|
|
if typ == u'void':
|
|
typ = None
|
|
elif typ != u'event' and typ not in types:
|
|
warning(u"Invalid type in %s, line %d: %s"
|
|
% (ubuiltins, linenum, typ))
|
|
continue
|
|
args = []
|
|
arglist = match.group(3)
|
|
if arglist:
|
|
arglist = arglist.split(u',')
|
|
bad = False
|
|
for arg in arglist:
|
|
argtyp = parse_arg_re.search(arg).group(1)
|
|
if argtyp not in types:
|
|
warning(u"Invalid type in %s, line %d: %s"
|
|
% (ubuiltins, linenum, argtyp))
|
|
bad = True
|
|
break
|
|
args.append(u2str(argtyp))
|
|
if bad:
|
|
continue
|
|
name = match.group(2)
|
|
if typ == u'event':
|
|
if name in events:
|
|
warning(u"Event at line %d was already defined in %s,"
|
|
u" overwriting: %s"
|
|
% (linenum, ubuiltins, name))
|
|
events[name] = {'pt':tuple(args), 'NeedsData':True}
|
|
else:
|
|
# Library functions go to the functions table. If
|
|
# they are implemented in lslfuncs.*, they get a
|
|
# reference to the implementation; otherwise None.
|
|
if name in functions:
|
|
warning(u"Function at line %d was already defined"
|
|
u" in %s, overwriting: %s"
|
|
% (linenum, ubuiltins, name))
|
|
fn = getattr(lslfuncs, name, None)
|
|
styp = None if typ is None else u2str(typ)
|
|
functions[name] = {'Kind':'f', 'Type':styp, 'uns':True,
|
|
'ParamTypes':args, 'NeedsData':True}
|
|
if fn is not None:
|
|
functions[name]['Fn'] = fn
|
|
elif match.group(4):
|
|
# constant
|
|
name = match.group(5)
|
|
if name in constants:
|
|
warning(u"Global at line %d was already defined in %s,"
|
|
u" overwriting: %s" % (linenum, ubuiltins, name))
|
|
typ = match.group(4)
|
|
if typ not in types:
|
|
warning(u"Invalid type in %s, line %d: %s"
|
|
% (ubuiltins, linenum, typ))
|
|
continue
|
|
if typ == u'quaternion':
|
|
typ = u'rotation'
|
|
value = match.group(6)
|
|
if typ == u'integer':
|
|
value = int(value, 0)
|
|
elif typ == u'float':
|
|
value = lslfuncs.F32(float(value))
|
|
elif typ == u'string':
|
|
if parse_str_re.search(value):
|
|
esc = False
|
|
tmp = value[1:-1]
|
|
value = u''
|
|
for c in tmp:
|
|
if esc:
|
|
if c == u'n':
|
|
c = u'\n'
|
|
elif c == u't':
|
|
c = u' '
|
|
value += c
|
|
esc = False
|
|
elif c == u'\\':
|
|
esc = True
|
|
else:
|
|
value += c
|
|
#if typ == 'key':
|
|
# value = Key(value)
|
|
else:
|
|
warning(u"Invalid string in %s line %d: %s"
|
|
% (ubuiltins, linenum, line))
|
|
value = None
|
|
elif typ == u'key':
|
|
warning(u"Key constants not supported in %s, line %d: %s"
|
|
% (ubuiltins, linenum, line))
|
|
value = None
|
|
elif typ in (u'vector', u'rotation'):
|
|
try:
|
|
if not (value.startswith(u'<') and value.endswith(u'>')
|
|
):
|
|
raise ValueError
|
|
value = value[1:-1].split(u',')
|
|
if len(value) != (3 if typ == 'vector' else 4):
|
|
raise ValueError
|
|
num = parse_fp_re.search(value[0])
|
|
if not num:
|
|
raise ValueError
|
|
value[0] = lslfuncs.F32(float(num.group(1)))
|
|
num = parse_fp_re.search(value[1])
|
|
if not num:
|
|
raise ValueError
|
|
value[1] = lslfuncs.F32(float(num.group(1)))
|
|
num = parse_fp_re.search(value[2])
|
|
if not num:
|
|
raise ValueError
|
|
value[2] = lslfuncs.F32(float(num.group(1)))
|
|
if typ == u'vector':
|
|
value = Vector(value)
|
|
else:
|
|
num = parse_fp_re.search(value[3])
|
|
if not num:
|
|
raise ValueError
|
|
value[3] = lslfuncs.F32(float(num.group(1)))
|
|
value = Quaternion(value)
|
|
except ValueError:
|
|
warning(u"Invalid vector/rotation syntax in %s"
|
|
u" line %d: %s" % (ubuiltins, linenum, line))
|
|
else:
|
|
assert typ == u'list'
|
|
if not (value.startswith(u'[') and value.endswith(u']')
|
|
):
|
|
warning(u"Invalid list value in %s, line %d: %s"
|
|
% (ubuiltins, linenum, line))
|
|
elif value[1:-1].strip() != '':
|
|
warning(u"Non-empty list constants not supported"
|
|
u" in %s, line %d: %s"
|
|
% (ubuiltins, linenum, line))
|
|
value = None
|
|
else:
|
|
value = []
|
|
if value is not None:
|
|
constants[name] = value
|
|
|
|
finally:
|
|
f.close()
|
|
|
|
# Load the function data table as well.
|
|
|
|
parse_flag_re = re.compile(str2u(r'^\s*-\s+(?:(?:(sef)|return\s+'
|
|
r'("(?:\\.|[^"])*"|<[^>]+>|[-+0-9x.e]+' # strings, vectors, numbers
|
|
r'|\[(?:[^]"]|"(?:\\.|[^"])*")*\]))' # lists
|
|
r'(?:\s+if\s+(.*\S))?'
|
|
r'|(unstable|stop|strlen|detect|touch|grab)'
|
|
r'|(min|max|delay)\s+([-0-9.]+)'
|
|
r'|listto\s+(integer|float|string|key|vector|rotation|list)'
|
|
r')\s*$'), re.I)
|
|
|
|
# TODO: "quaternion" doesn't compare equal to "rotation" even if they are
|
|
# equivalent. Canonicalize it before comparison, to avoid false
|
|
# reports of mismatches.
|
|
f = open(fndata, 'rb')
|
|
try:
|
|
linenum = 0
|
|
curr_fn = None
|
|
curr_ty = None
|
|
skipping = False
|
|
try:
|
|
ufndata = str2u(fndata, sys.getfilesystemencoding())
|
|
except UnicodeDecodeError:
|
|
# This is just a guess at the filename encoding.
|
|
ufndata = str2u(fndata, 'iso-8859-15')
|
|
while True:
|
|
linenum += 1
|
|
line = f.readline()
|
|
if not line: break
|
|
if line.endswith(b'\r\n'): line = line[:-2]
|
|
if line.endswith(b'\n'): line = line[:-1]
|
|
try:
|
|
line = b2u(line, 'utf8')
|
|
except UnicodeDecodeError:
|
|
warning(u"Bad Unicode in %s line %d" % (ufndata, linenum))
|
|
continue
|
|
match_fn = parse_lin_re.search(line)
|
|
if match_fn and not match_fn.group(4) and not match_fn.group(1):
|
|
# comment or empty
|
|
continue
|
|
|
|
rettype = match_fn.group(1) if match_fn else None
|
|
if match_fn and (rettype in (u'void', u'event') or rettype in types):
|
|
skipping = True # until proven otherwise
|
|
name = match_fn.group(2)
|
|
if (rettype == u'event' and name not in events
|
|
or rettype != u'event' and name not in functions
|
|
):
|
|
warning(u"%s %s is not in builtins, in %s line %d,"
|
|
u" skipping."
|
|
% (u"Function" if rettype != 'event' else u"Event",
|
|
name, ufndata, linenum))
|
|
continue
|
|
rettype = rettype if rettype != 'void' else None
|
|
if 'event' != rettype != functions[name]['Type']:
|
|
warning(u"Function %s returns invalid type, in %s line %d,"
|
|
u" skipping."
|
|
% (name, ufndata, linenum))
|
|
continue
|
|
argnames = []
|
|
arglist = match_fn.group(3)
|
|
current_args = (functions[name]['ParamTypes']
|
|
if rettype != 'event'
|
|
else events[name]['pt'])
|
|
if arglist:
|
|
arglist = arglist.split(u',')
|
|
if len(current_args) != len(arglist):
|
|
warning(u"Parameter list mismatch in %s line %d,"
|
|
u" %s %s. Skipping."
|
|
% (ufndata, linenum,
|
|
u"function" if rettype != u'event'
|
|
else u"event", name))
|
|
continue
|
|
|
|
bad = False # used to 'continue' at this loop level
|
|
for idx, arg in enumerate(arglist):
|
|
argmatch = parse_arg_re.search(arg)
|
|
argtyp = argmatch.group(1)
|
|
argname = argmatch.group(2)
|
|
if current_args[idx] != argtyp:
|
|
warning(u"Parameter list mismatch in %s line %d,"
|
|
u" %s %s. Skipping."
|
|
% (ufndata, linenum,
|
|
u"function" if rettype != u'event'
|
|
else u"event", name))
|
|
bad = True
|
|
break
|
|
argnames.append(argname)
|
|
if bad:
|
|
del bad
|
|
continue
|
|
del bad
|
|
|
|
if 'NeedsData' not in (functions[name] if rettype != 'event'
|
|
else events[name]):
|
|
warning(u"Duplicate %s %s in %s line %d. Skipping."
|
|
% (u"function" if rettype != 'event' else u"event",
|
|
name, ufndata, linenum))
|
|
continue
|
|
|
|
# passed all tests
|
|
curr_fn = name
|
|
curr_ty = rettype
|
|
skipping = False
|
|
if curr_ty == 'event':
|
|
del events[name]['NeedsData']
|
|
else:
|
|
del functions[name]['NeedsData'], functions[name]['uns']
|
|
|
|
else:
|
|
match_flag = parse_flag_re.search(line)
|
|
if match_flag:
|
|
if curr_fn is None and not skipping:
|
|
warning(u"Flags present before any function or event"
|
|
u" in %s line %d: %s"
|
|
% (ufndata, linenum, line))
|
|
skipping = True
|
|
continue
|
|
if not skipping:
|
|
if match_flag.group(1):
|
|
# SEF
|
|
# We don't handle conditions yet. Take the
|
|
# condition as never met for now (every function
|
|
# that is conditionally SEF is taken as not SEF)
|
|
if curr_ty == 'event' and match_flag.group(3):
|
|
warning(u"Events do not support conditions"
|
|
u" in SEF flags, in line %d, event %s."
|
|
u" Omitting: %s"
|
|
% (linenum, curr_fn, line))
|
|
continue
|
|
elif curr_ty == 'event':
|
|
events[curr_fn]['SEF'] = True
|
|
else:
|
|
if not match_flag.group(3):
|
|
functions[curr_fn]['SEF'] = True
|
|
|
|
elif curr_ty == 'event':
|
|
if match_flag.group(4):
|
|
flag = match_flag.group(4).lower()
|
|
if flag in ('detect','touch','grab'):
|
|
events[curr_fn][flag] = True
|
|
else:
|
|
warning(u"Events only support a few flags"
|
|
u", in line %d, event %s."
|
|
u" Omitting: %s"
|
|
% (linenum, curr_fn, line))
|
|
continue
|
|
elif match_flag.group(2):
|
|
pass # return not handled yet
|
|
elif match_flag.group(4):
|
|
flag = match_flag.group(4).lower()
|
|
if flag == 'unstable':
|
|
if functions[curr_fn]['Type'] is None:
|
|
warning(u"unstable does not make sense"
|
|
" with void function, in line %d,"
|
|
" function %s. Omitting flag."
|
|
% (linenum, curr_fn))
|
|
else:
|
|
functions[curr_fn]['uns'] = True
|
|
else:
|
|
functions[curr_fn][flag] = True
|
|
elif match_flag.group(5):
|
|
if match_flag.group(5).lower() in ('min', 'max'):
|
|
minmax = match_flag.group(5).lower()
|
|
value = match_flag.group(6)
|
|
typ = functions[curr_fn]['Type']
|
|
if typ == 'integer':
|
|
good = parse_int_re.search(value)
|
|
if good:
|
|
value = lslfuncs.S32(int(good.group(1), 0))
|
|
elif typ == 'float':
|
|
good = parse_fp_re.search(value)
|
|
if good:
|
|
value = lslfuncs.F32(float(good.group(1)))
|
|
else:
|
|
good = False
|
|
if good:
|
|
functions[curr_fn][minmax] = value
|
|
else:
|
|
warning(u"Type mismatch or value error in %s"
|
|
u" line %d: %s"
|
|
% (ufndata, linenum, line))
|
|
continue
|
|
else: # delay
|
|
value = parse_fp_re.search(match_flag.group(6))
|
|
if not value:
|
|
warning(u"Invalid delay value in %s"
|
|
u" line %d: %s"
|
|
% (ufndata, linenum, line))
|
|
continue
|
|
|
|
value = float(value.group(1)) # no need to F32
|
|
if value != 0 and 'SEF' in functions[curr_fn]:
|
|
warning(u"Side-effect-free function"
|
|
u" %s contradicts delay, in %s"
|
|
u" line %d"
|
|
% (curr_fn, ufndata, linenum))
|
|
continue
|
|
|
|
functions[curr_fn]['delay'] = value
|
|
elif match_flag.group(7):
|
|
functions[curr_fn]['ListTo'] = match_flag.group(7)
|
|
continue
|
|
else:
|
|
pass
|
|
else:
|
|
warning(u"Syntax error in %s line %d, skipping: %s"
|
|
% (ufndata, linenum, line))
|
|
continue
|
|
|
|
finally:
|
|
f.close()
|
|
|
|
# Post-checks
|
|
for i in functions:
|
|
if 'NeedsData' in functions[i]:
|
|
del functions[i]['NeedsData']
|
|
warning(u"Library data, file %s: Function %s has no data."
|
|
% (ufndata, i))
|
|
if 'min' in functions[i] and 'max' in functions[i]:
|
|
if functions[i]['min'] > functions[i]['max']:
|
|
warning(u"Library data: Function %s has min > max:"
|
|
u" min=%s max=%s, removing both."
|
|
% (i, str2u(repr(functions[i]['min']), 'utf8'),
|
|
repr(functions[i]['max'])))
|
|
del functions[i]['min'], functions[i]['max']
|
|
if 'SEF' in functions[i] and 'delay' in functions[i]:
|
|
warning(u"Library data: Side-effect-free function %s contradicts"
|
|
u" delay. Removing SEF." % i)
|
|
del functions[i]['SEF']
|
|
for i in events:
|
|
if 'NeedsData' in events[i]:
|
|
del events[i]['NeedsData']
|
|
warning(u"Library data, file %s: Event %s has no data."
|
|
% (ufndata, i))
|
|
|
|
return events, constants, functions
|