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):
def __init__(self):
super(self.ELSLMathError, self).__init__("Math Error")
super(ELSLMathError, self).__init__("Math Error")
class ELSLInvalidType(Exception):
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 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))
if tb == Quaternion: # division by a rotation is multiplication by the conjugate of the rotation
# 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
def mod(a, b, f32=True):
# defined only for integers and vectors
if type(a) == type(b) == int:
if b == 0:
raise ELSLMathError
if a < 0:
return int(-((-a) % abs(b)))
return int(a % abs(b))
@ -634,7 +639,7 @@ def compare(a, b, Eq = True):
else:
ret = a == b
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
return int(not ret) if Eq else ret
if ta == tb in (Vector, Quaternion):
@ -779,15 +784,19 @@ def llAxisAngle2Rot(axis, angle):
s = math.sin(ff(angle)*0.5)
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
# on the garbage bytes returned. We implement it deterministically.
# NOTE: This one does not always return the same value in LSL. When it isn't
# deterministic, it raises ELSLCantCompute.
def llBase64ToInteger(s):
assert isstring(s)
if len(s) > 8:
return 0
s = b64_re.match(s).group()
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
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
# we don't control that here. Instead we return the integer-less
# vector when asked.
return v2q(elem)
return v2f(elem)
except IndexError:
pass
return ZERO_VECTOR
@ -1341,7 +1350,7 @@ def llRot2Angle(r):
def llRot2Axis(r):
assert isrotation(r)
r = q2f(r)
return llVecNorm((r[0], r[1], r[2]))
return llVecNorm(Vector((r[0], r[1], r[2])))
def llRot2Euler(r):
assert isrotation(r)
@ -1553,7 +1562,11 @@ def llVecDist(v1, v2):
assert isvector(v2)
v1 = v2f(v1)
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):
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])))
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):
assert isstring(s)
assert isstring(xor)
@ -1586,9 +1596,10 @@ def llXorBase64(s, xor):
L2 = len(xor)
if L2 == 0:
# This is not accurate. This returns garbage (of undefined length) in LSL.
# The first returned byte seems to be zero always though.
xor = u'ABCD';
# The input xor string starts with zero or one valid Base64 characters.
# This produces garbage bytes (the first byte is zero though).
# We don't produce a result in this case.
raise ELSLCantCompute
s = b64decode(s + u'='*(-L1 & 3))
xor = b64decode(xor + u'='*(-L2 & 3))
@ -1670,9 +1681,10 @@ def llXorBase64StringsCorrect(s, xor):
L2 = len(xor)
if L2 == 0:
# This is not accurate. This returns garbage (of length 4?) in LSL.
# The first returned byte seems to be zero always though.
xor = u'ABCD'
# The input xor string starts with zero or one valid Base64 characters.
# This produces garbage bytes (the first byte is zero though).
# We don't produce a result in this case.
raise ELSLCantCompute
s = b64decode(s + u'='*(-L1 & 3))
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 == '*':
result = lslfuncs.mul(op1, op2)
elif nt == '/':
try:
result = lslfuncs.div(op1, op2)
except lslfuncs.ELSLMathError:
return
elif nt == '%':
try:
result = lslfuncs.mod(op1, op2)
except lslfuncs.ELSLMathError:
return
elif nt == '<<':
result = lslfuncs.S32(op1 << (op2 & 31))
elif nt == '>>':
@ -559,11 +565,19 @@ class foldconst(object):
if CONSTargs:
# Call it
fn = self.symtab[0][node['name']]['Fn']
try:
if node['name'][:10] == 'llDetected':
value = fn(*tuple(arg['value'] for arg in child),
event=self.CurEvent)
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':
# Convert llGetListLength(ident) to (ident != [])
node = {'nt':'CONST', 't':'list', 'value':[]}
@ -588,6 +602,13 @@ class foldconst(object):
return
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)
if 'SEF' in child[0]:
node['SEF'] = True
@ -829,6 +850,7 @@ class foldconst(object):
self.globalmode = False
tree = self.tree
self.CurEvent = None
# Constant folding pass. It does some other optimizations along the way.
for idx in xrange(len(tree)):

View file

@ -1,3 +1,4 @@
# Put all LSL functions together in one single module
from lslbasefuncs 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
# declarations.
ret[name] = {'Kind':'v','Type':typ,'Scope':0}
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()
except EParseUEOF:

View file

@ -37,6 +37,7 @@ Options (+ means active by default, - means inactive by default):
optimize + Runs the optimizer.
optsigns + Optimize signs in float and integer constants.
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
default. But with support from the viewer, they can be
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',
'allowkeyconcat','allowmultistrings','skippreproc','optimize',
'optsigns','optfloats'
'optsigns','optfloats','constfold'
))
if sys.argv[1] == '-O':