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:
Sei Lisa 2015-02-11 05:43:13 +01:00
parent 716be215f2
commit db862bb4a6
6 changed files with 175 additions and 28 deletions

View file

@ -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
View 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

View file

@ -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)):

View file

@ -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 *

View file

@ -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:

View file

@ -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':