diff --git a/lslopt/lslbasefuncs.py b/lslopt/lslbasefuncs.py index ef6fc2f..eed0590 100644 --- a/lslopt/lslbasefuncs.py +++ b/lslopt/lslbasefuncs.py @@ -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' diff --git a/lslopt/lslextrafuncs.py b/lslopt/lslextrafuncs.py new file mode 100644 index 0000000..fa4c2b6 --- /dev/null +++ b/lslopt/lslextrafuncs.py @@ -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 + diff --git a/lslopt/lslfoldconst.py b/lslopt/lslfoldconst.py index 46deb1a..9c0d920 100644 --- a/lslopt/lslfoldconst.py +++ b/lslopt/lslfoldconst.py @@ -195,9 +195,15 @@ class foldconst(object): elif nt == '*': result = lslfuncs.mul(op1, op2) elif nt == '/': - result = lslfuncs.div(op1, op2) + try: + result = lslfuncs.div(op1, op2) + except lslfuncs.ELSLMathError: + return elif nt == '%': - result = lslfuncs.mod(op1, op2) + 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'] - 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} + 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)): diff --git a/lslopt/lslfuncs.py b/lslopt/lslfuncs.py index 8bf8f5e..81486e1 100644 --- a/lslopt/lslfuncs.py +++ b/lslopt/lslfuncs.py @@ -1,3 +1,4 @@ # Put all LSL functions together in one single module from lslbasefuncs import * from lsljson import * +from lslextrafuncs import * diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index de87cc2..16bdcc3 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -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: diff --git a/main.py b/main.py index 4555ce5..80206aa 100644 --- a/main.py +++ b/main.py @@ -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':