mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-01 23:58:20 +00:00
Multi-commit:
- Fix a bunch of bugs found during the debut of the LSL calculator. - Add infrastructure for functions to be able to produce a result or not depending on arguments. Fixes the llBase64ToInteger/llXorBase64/llXorBase64StringsCorrect cases where they are not deterministic, and allows for the addition of some extra functions whose value can be determined in some cases (e.g. llDetectedType(-1) is always 0). Added several such functions in a new module. - Add the constant folding option to the help and the default options.
This commit is contained in:
parent
716be215f2
commit
db862bb4a6
6 changed files with 175 additions and 28 deletions
|
@ -54,11 +54,14 @@ class ELSLTypeMismatch(Exception):
|
||||||
|
|
||||||
class ELSLMathError(Exception):
|
class ELSLMathError(Exception):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(self.ELSLMathError, self).__init__("Math Error")
|
super(ELSLMathError, self).__init__("Math Error")
|
||||||
|
|
||||||
class ELSLInvalidType(Exception):
|
class ELSLInvalidType(Exception):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(self.ELSLInvalidType, self).__init__("Internal error: Invalid type")
|
super(ELSLInvalidType, self).__init__("Internal error: Invalid type")
|
||||||
|
|
||||||
|
class ELSLCantCompute(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
# LSL types are translated to Python types as follows:
|
# LSL types are translated to Python types as follows:
|
||||||
# * LSL string -> Python unicode
|
# * LSL string -> Python unicode
|
||||||
|
@ -604,12 +607,14 @@ def div(a, b, f32=True):
|
||||||
return Vector(F32((a[0]/b, a[1]/b, a[2]/b), f32))
|
return Vector(F32((a[0]/b, a[1]/b, a[2]/b), f32))
|
||||||
if tb == Quaternion: # division by a rotation is multiplication by the conjugate of the rotation
|
if tb == Quaternion: # division by a rotation is multiplication by the conjugate of the rotation
|
||||||
# defer the remaining type checks to mul()
|
# defer the remaining type checks to mul()
|
||||||
return mul(a, (-b[0],-b[1],-b[2],b[3]), f32)
|
return mul(a, Quaternion((-b[0],-b[1],-b[2],b[3])), f32)
|
||||||
raise ELSLTypeMismatch
|
raise ELSLTypeMismatch
|
||||||
|
|
||||||
def mod(a, b, f32=True):
|
def mod(a, b, f32=True):
|
||||||
# defined only for integers and vectors
|
# defined only for integers and vectors
|
||||||
if type(a) == type(b) == int:
|
if type(a) == type(b) == int:
|
||||||
|
if b == 0:
|
||||||
|
raise ELSLMathError
|
||||||
if a < 0:
|
if a < 0:
|
||||||
return int(-((-a) % abs(b)))
|
return int(-((-a) % abs(b)))
|
||||||
return int(a % abs(b))
|
return int(a % abs(b))
|
||||||
|
@ -634,7 +639,7 @@ def compare(a, b, Eq = True):
|
||||||
else:
|
else:
|
||||||
ret = a == b
|
ret = a == b
|
||||||
return int(ret) if Eq else 1-ret
|
return int(ret) if Eq else 1-ret
|
||||||
if ta in (unicode, Key) and tb in (unicode, key):
|
if ta in (unicode, Key) and tb in (unicode, Key):
|
||||||
ret = 0 if a == b else 1 if a > b or not lslcommon.LSO else -1
|
ret = 0 if a == b else 1 if a > b or not lslcommon.LSO else -1
|
||||||
return int(not ret) if Eq else ret
|
return int(not ret) if Eq else ret
|
||||||
if ta == tb in (Vector, Quaternion):
|
if ta == tb in (Vector, Quaternion):
|
||||||
|
@ -779,15 +784,19 @@ def llAxisAngle2Rot(axis, angle):
|
||||||
s = math.sin(ff(angle)*0.5)
|
s = math.sin(ff(angle)*0.5)
|
||||||
return Quaternion(F32((axis[0]*s, axis[1]*s, axis[2]*s, c)))
|
return Quaternion(F32((axis[0]*s, axis[1]*s, axis[2]*s, c)))
|
||||||
|
|
||||||
# NOTE: This one does not always return the same value in LSL, but no one should depend
|
# NOTE: This one does not always return the same value in LSL. When it isn't
|
||||||
# on the garbage bytes returned. We implement it deterministically.
|
# deterministic, it raises ELSLCantCompute.
|
||||||
def llBase64ToInteger(s):
|
def llBase64ToInteger(s):
|
||||||
assert isstring(s)
|
assert isstring(s)
|
||||||
if len(s) > 8:
|
if len(s) > 8:
|
||||||
return 0
|
return 0
|
||||||
s = b64_re.match(s).group()
|
s = b64_re.match(s).group()
|
||||||
i = len(s)
|
i = len(s)
|
||||||
s = (b64decode(s + u'='*(-i & 3)) + b'\0\0\0\0')[:4] # actually the last 3 bytes should be garbage
|
s = b64decode(s + u'='*(-i & 3))
|
||||||
|
if len(s) < 3:
|
||||||
|
# not computable deterministically
|
||||||
|
raise ELSLCantCompute
|
||||||
|
s = (s + b'\0')[:4]
|
||||||
i = ord(s[0]) if s[0] < b'\x80' else ord(s[0])-256
|
i = ord(s[0]) if s[0] < b'\x80' else ord(s[0])-256
|
||||||
return (i<<24)+(ord(s[1])<<16)+(ord(s[2])<<8)+ord(s[3])
|
return (i<<24)+(ord(s[1])<<16)+(ord(s[2])<<8)+ord(s[3])
|
||||||
|
|
||||||
|
@ -1040,7 +1049,7 @@ def llList2Vector(lst, pos):
|
||||||
# The list should not contain integer vector components, but
|
# The list should not contain integer vector components, but
|
||||||
# we don't control that here. Instead we return the integer-less
|
# we don't control that here. Instead we return the integer-less
|
||||||
# vector when asked.
|
# vector when asked.
|
||||||
return v2q(elem)
|
return v2f(elem)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
return ZERO_VECTOR
|
return ZERO_VECTOR
|
||||||
|
@ -1341,7 +1350,7 @@ def llRot2Angle(r):
|
||||||
def llRot2Axis(r):
|
def llRot2Axis(r):
|
||||||
assert isrotation(r)
|
assert isrotation(r)
|
||||||
r = q2f(r)
|
r = q2f(r)
|
||||||
return llVecNorm((r[0], r[1], r[2]))
|
return llVecNorm(Vector((r[0], r[1], r[2])))
|
||||||
|
|
||||||
def llRot2Euler(r):
|
def llRot2Euler(r):
|
||||||
assert isrotation(r)
|
assert isrotation(r)
|
||||||
|
@ -1553,7 +1562,11 @@ def llVecDist(v1, v2):
|
||||||
assert isvector(v2)
|
assert isvector(v2)
|
||||||
v1 = v2f(v1)
|
v1 = v2f(v1)
|
||||||
v2 = v2f(v2)
|
v2 = v2f(v2)
|
||||||
return llVecMag((v1[0]-v2[0],v1[1]-v2[1],v1[2]-v2[2]))
|
# For improved accuracy, do the intermediate calcs as doubles
|
||||||
|
vx = v1[0]-v2[0]
|
||||||
|
vy = v1[1]-v2[1]
|
||||||
|
vz = v1[2]-v2[2]
|
||||||
|
return F32(math.sqrt(math.fsum((vx*vx, vy*vy, vz*vz))))
|
||||||
|
|
||||||
def llVecMag(v):
|
def llVecMag(v):
|
||||||
assert isvector(v)
|
assert isvector(v)
|
||||||
|
@ -1568,9 +1581,6 @@ def llVecNorm(v, f32 = True):
|
||||||
f = math.sqrt(math.fsum((v[0]*v[0], v[1]*v[1], v[2]*v[2])))
|
f = math.sqrt(math.fsum((v[0]*v[0], v[1]*v[1], v[2]*v[2])))
|
||||||
return F32(Vector((v[0]/f,v[1]/f,v[2]/f)), f32)
|
return F32(Vector((v[0]/f,v[1]/f,v[2]/f)), f32)
|
||||||
|
|
||||||
# NOTE: llXorBase64 returns garbage bytes if the input xor string
|
|
||||||
# starts with zero or one valid Base64 characters. We don't emulate that here;
|
|
||||||
# our output is deterministic.
|
|
||||||
def llXorBase64(s, xor):
|
def llXorBase64(s, xor):
|
||||||
assert isstring(s)
|
assert isstring(s)
|
||||||
assert isstring(xor)
|
assert isstring(xor)
|
||||||
|
@ -1586,9 +1596,10 @@ def llXorBase64(s, xor):
|
||||||
L2 = len(xor)
|
L2 = len(xor)
|
||||||
|
|
||||||
if L2 == 0:
|
if L2 == 0:
|
||||||
# This is not accurate. This returns garbage (of undefined length) in LSL.
|
# The input xor string starts with zero or one valid Base64 characters.
|
||||||
# The first returned byte seems to be zero always though.
|
# This produces garbage bytes (the first byte is zero though).
|
||||||
xor = u'ABCD';
|
# We don't produce a result in this case.
|
||||||
|
raise ELSLCantCompute
|
||||||
|
|
||||||
s = b64decode(s + u'='*(-L1 & 3))
|
s = b64decode(s + u'='*(-L1 & 3))
|
||||||
xor = b64decode(xor + u'='*(-L2 & 3))
|
xor = b64decode(xor + u'='*(-L2 & 3))
|
||||||
|
@ -1670,9 +1681,10 @@ def llXorBase64StringsCorrect(s, xor):
|
||||||
L2 = len(xor)
|
L2 = len(xor)
|
||||||
|
|
||||||
if L2 == 0:
|
if L2 == 0:
|
||||||
# This is not accurate. This returns garbage (of length 4?) in LSL.
|
# The input xor string starts with zero or one valid Base64 characters.
|
||||||
# The first returned byte seems to be zero always though.
|
# This produces garbage bytes (the first byte is zero though).
|
||||||
xor = u'ABCD'
|
# We don't produce a result in this case.
|
||||||
|
raise ELSLCantCompute
|
||||||
|
|
||||||
s = b64decode(s + u'='*(-L1 & 3))
|
s = b64decode(s + u'='*(-L1 & 3))
|
||||||
xor = b64decode(xor + u'='*(-L2 & 3)) + b'\x00'
|
xor = b64decode(xor + u'='*(-L2 & 3)) + b'\x00'
|
||||||
|
|
110
lslopt/lslextrafuncs.py
Normal file
110
lslopt/lslextrafuncs.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
from lslcommon import *
|
||||||
|
from lslbasefuncs import ELSLCantCompute, isinteger, \
|
||||||
|
isvector, NULL_KEY, ZERO_VECTOR, ZERO_ROTATION
|
||||||
|
#isfloat, isstring, iskey, isrotation, islist
|
||||||
|
|
||||||
|
TouchEvents = ('touch', 'touch_start', 'touch_end')
|
||||||
|
DetectionEvents = ('touch', 'touch_start', 'touch_end',
|
||||||
|
'collision', 'collision_start', 'collision_end',
|
||||||
|
'sensor')
|
||||||
|
|
||||||
|
def llCloud(v):
|
||||||
|
assert isvector(v)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def llAvatarOnLinkSitTarget(link):
|
||||||
|
assert isinteger(link)
|
||||||
|
if link > 255 or link == -2147483648:
|
||||||
|
return Key(NULL_KEY)
|
||||||
|
raise ELSLCantCompute
|
||||||
|
|
||||||
|
def llDetectedGrab(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event == 'touch' or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedGroup(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def llDetectedKey(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return Key(NULL_KEY)
|
||||||
|
|
||||||
|
def llDetectedLinkNumber(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def llDetectedName(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return u''
|
||||||
|
|
||||||
|
def llDetectedOwner(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return Key(NULL_KEY)
|
||||||
|
|
||||||
|
def llDetectedPos(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedRot(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_ROTATION
|
||||||
|
|
||||||
|
def llDetectedTouchBinormal(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in TouchEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedTouchFace(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in TouchEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def llDetectedTouchNormal(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in TouchEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedTouchPos(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in TouchEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedTouchST(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in TouchEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedTouchUV(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in TouchEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return ZERO_VECTOR
|
||||||
|
|
||||||
|
def llDetectedType(idx, event=None):
|
||||||
|
assert isinteger(idx)
|
||||||
|
if 0 <= idx <= 15 and (event in DetectionEvents or event is None):
|
||||||
|
raise ELSLCantCompute
|
||||||
|
return 0
|
||||||
|
|
|
@ -195,9 +195,15 @@ class foldconst(object):
|
||||||
elif nt == '*':
|
elif nt == '*':
|
||||||
result = lslfuncs.mul(op1, op2)
|
result = lslfuncs.mul(op1, op2)
|
||||||
elif nt == '/':
|
elif nt == '/':
|
||||||
result = lslfuncs.div(op1, op2)
|
try:
|
||||||
|
result = lslfuncs.div(op1, op2)
|
||||||
|
except lslfuncs.ELSLMathError:
|
||||||
|
return
|
||||||
elif nt == '%':
|
elif nt == '%':
|
||||||
result = lslfuncs.mod(op1, op2)
|
try:
|
||||||
|
result = lslfuncs.mod(op1, op2)
|
||||||
|
except lslfuncs.ELSLMathError:
|
||||||
|
return
|
||||||
elif nt == '<<':
|
elif nt == '<<':
|
||||||
result = lslfuncs.S32(op1 << (op2 & 31))
|
result = lslfuncs.S32(op1 << (op2 & 31))
|
||||||
elif nt == '>>':
|
elif nt == '>>':
|
||||||
|
@ -559,11 +565,19 @@ class foldconst(object):
|
||||||
if CONSTargs:
|
if CONSTargs:
|
||||||
# Call it
|
# Call it
|
||||||
fn = self.symtab[0][node['name']]['Fn']
|
fn = self.symtab[0][node['name']]['Fn']
|
||||||
value = fn(*tuple(arg['value'] for arg in child))
|
try:
|
||||||
if not self.foldtabs and isinstance(value, unicode) and '\t' in value:
|
if node['name'][:10] == 'llDetected':
|
||||||
warning('Tab in function result and foldtabs option not used.')
|
value = fn(*tuple(arg['value'] for arg in child),
|
||||||
return
|
event=self.CurEvent)
|
||||||
parent[index] = {'nt':'CONST', 't':node['t'], 'value':value}
|
else:
|
||||||
|
value = fn(*tuple(arg['value'] for arg in child))
|
||||||
|
if not self.foldtabs and isinstance(value, unicode) and '\t' in value:
|
||||||
|
warning('Tab in function result and foldtabs option not used.')
|
||||||
|
return
|
||||||
|
parent[index] = {'nt':'CONST', 't':node['t'], 'value':value}
|
||||||
|
except lslfuncs.ELSLCantCompute:
|
||||||
|
# Don't transform the tree if function is not computable
|
||||||
|
pass
|
||||||
elif node['name'] == 'llGetListLength' and child[0]['nt'] == 'IDENT':
|
elif node['name'] == 'llGetListLength' and child[0]['nt'] == 'IDENT':
|
||||||
# Convert llGetListLength(ident) to (ident != [])
|
# Convert llGetListLength(ident) to (ident != [])
|
||||||
node = {'nt':'CONST', 't':'list', 'value':[]}
|
node = {'nt':'CONST', 't':'list', 'value':[]}
|
||||||
|
@ -588,6 +602,13 @@ class foldconst(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
if nt == 'FNDEF':
|
if nt == 'FNDEF':
|
||||||
|
# used when folding llDetected* function calls
|
||||||
|
if 'scope' in node:
|
||||||
|
# function definition
|
||||||
|
self.CurEvent = None
|
||||||
|
else:
|
||||||
|
# event definition
|
||||||
|
self.CurEvent = node['name']
|
||||||
self.FoldTree(child, 0)
|
self.FoldTree(child, 0)
|
||||||
if 'SEF' in child[0]:
|
if 'SEF' in child[0]:
|
||||||
node['SEF'] = True
|
node['SEF'] = True
|
||||||
|
@ -829,6 +850,7 @@ class foldconst(object):
|
||||||
self.globalmode = False
|
self.globalmode = False
|
||||||
|
|
||||||
tree = self.tree
|
tree = self.tree
|
||||||
|
self.CurEvent = None
|
||||||
|
|
||||||
# Constant folding pass. It does some other optimizations along the way.
|
# Constant folding pass. It does some other optimizations along the way.
|
||||||
for idx in xrange(len(tree)):
|
for idx in xrange(len(tree)):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
# Put all LSL functions together in one single module
|
# Put all LSL functions together in one single module
|
||||||
from lslbasefuncs import *
|
from lslbasefuncs import *
|
||||||
from lsljson import *
|
from lsljson import *
|
||||||
|
from lslextrafuncs import *
|
||||||
|
|
|
@ -1754,8 +1754,9 @@ class parser(object):
|
||||||
# No location info but none is necessary for forward
|
# No location info but none is necessary for forward
|
||||||
# declarations.
|
# declarations.
|
||||||
ret[name] = {'Kind':'v','Type':typ,'Scope':0}
|
ret[name] = {'Kind':'v','Type':typ,'Scope':0}
|
||||||
|
|
||||||
while self.tok[0] != ';': # Don't stop to analyze what's before the ending ';'
|
while self.tok[0] != ';': # Don't stop to analyze what's before the ending ';'
|
||||||
|
if self.tok[0] == 'EOF':
|
||||||
|
return ret
|
||||||
self.NextToken()
|
self.NextToken()
|
||||||
self.NextToken()
|
self.NextToken()
|
||||||
except EParseUEOF:
|
except EParseUEOF:
|
||||||
|
|
3
main.py
3
main.py
|
@ -37,6 +37,7 @@ Options (+ means active by default, - means inactive by default):
|
||||||
optimize + Runs the optimizer.
|
optimize + Runs the optimizer.
|
||||||
optsigns + Optimize signs in float and integer constants.
|
optsigns + Optimize signs in float and integer constants.
|
||||||
optfloats + Optimize a float when it is an integral value.
|
optfloats + Optimize a float when it is an integral value.
|
||||||
|
constfold + Fold constant expressions to their values.
|
||||||
foldtabs - Tabs can't be copy-pasted, so they aren't optimized by
|
foldtabs - Tabs can't be copy-pasted, so they aren't optimized by
|
||||||
default. But with support from the viewer, they can be
|
default. But with support from the viewer, they can be
|
||||||
folded too and make it to the uploaded source. This
|
folded too and make it to the uploaded source. This
|
||||||
|
@ -62,7 +63,7 @@ means that e.g. a + 3 + 5 is not optimized to a + 8; however a + (3 + 5) is.
|
||||||
|
|
||||||
options = set(('extendedglobalexpr','extendedtypecast','extendedassignment',
|
options = set(('extendedglobalexpr','extendedtypecast','extendedassignment',
|
||||||
'allowkeyconcat','allowmultistrings','skippreproc','optimize',
|
'allowkeyconcat','allowmultistrings','skippreproc','optimize',
|
||||||
'optsigns','optfloats'
|
'optsigns','optfloats','constfold'
|
||||||
))
|
))
|
||||||
|
|
||||||
if sys.argv[1] == '-O':
|
if sys.argv[1] == '-O':
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue