mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2024-11-21 06:15:56 -07:00
2482 lines
75 KiB
Python
2482 lines
75 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/>.
|
|
|
|
# This module is used by the optimizer for resolving constant values.
|
|
#
|
|
# The functions it implements are all functions that always return the same
|
|
# result when given the same input, and that have no side effects.
|
|
#
|
|
# For example, llAbs() is here, but llGetPos() is not, because it doesn't
|
|
# always return the same result.
|
|
#
|
|
# This implies that functions present in this module can be precomputed if
|
|
# their arguments are constants.
|
|
#
|
|
# In some instances, the result can't be computed; in these cases the function
|
|
# raises a LSLCantCompute exception that is caught by the optimizer to leave
|
|
# the expression unchanged. For example, llXorBase64Strings has a delay, so it
|
|
# can't just be replaced by its value; llFrand produces unpredictable results
|
|
# (by design) in most cases, but not all.
|
|
#
|
|
# The JSON functions have been separated to their own module.
|
|
|
|
import re
|
|
from lslopt.lslcommon import *
|
|
from lslopt import lslcommon
|
|
from ctypes import c_float
|
|
import math
|
|
import hashlib
|
|
from base64 import b64encode, b64decode
|
|
from strutil import *
|
|
|
|
|
|
# Regular expressions used along the code. They are needed mainly because
|
|
# Python lacks a C-like strtod/strtol (it comes close, but it is very picky
|
|
# with what it accepts). We need to extract the number part of a string, or
|
|
# Python will complain.
|
|
# Also, Base64 needs the correct count of characters (len mod 4 can't be = 1).
|
|
# The RE helps both in isolating the Base64 section and in trimming out the
|
|
# offending characters; it just doesn't help with padding, with which Python is
|
|
# also picky. We deal with that in the code by padding with '='*(-length & 3).
|
|
|
|
# Despite what http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Floats.html#Parsing-of-Floats
|
|
# says, NaN(chars) does not work in LSL (which is relevant in vectors).
|
|
# Note infinity vs. inf is necessary for parsing vectors & rotations,
|
|
# e.g. (vector)"<1,inf,infix>" is not valid but (vector)"<1,inf,infinity>" is
|
|
# as is (vector)"<1,inf,info>". The 1st gives <0,0,0>, the others <1,inf,inf>.
|
|
# The lookahead (?!i) is essential for parsing them that way without extra code.
|
|
# Note that '|' in REs is order-sensitive.
|
|
float_re_tpl = r'''
|
|
^\s*[+-]?(?:
|
|
0(x)(?: # Hex float or hex int (captures the 'x')
|
|
[0-9a-f]+(?:\.[0-9a-f]*)?
|
|
|\.[0-9a-f]+ # Hex digits
|
|
)(?:
|
|
p[+-]?[0-9]+ # Hex float exponent
|
|
)? # (optional)
|
|
|(?: # Decimal float or decimal int
|
|
[0-9]+(?:\.[0-9]*)?
|
|
|\.[0-9]+ # Decimal digits
|
|
)(?:
|
|
e[+-]?[0-9]+ # Decimal float exponent
|
|
)? # (optional)
|
|
|%s # Infinity (normal or vector variant)
|
|
|(nan) # NaN (captured)
|
|
)
|
|
'''
|
|
float_re = re.compile(str2u(float_re_tpl % 'inf'), re.I | re.X)
|
|
vfloat_re = re.compile(str2u(float_re_tpl % 'infinity|inf(?!i)'), re.I | re.X)
|
|
del float_re_tpl
|
|
|
|
int_re = re.compile(str2u(r'^0(x)[0-9a-f]+|^\s*[+-]?[0-9]+'), re.I)
|
|
|
|
key_re = re.compile(str2u(r'^[0-9a-f]{8}(?:-[0-9a-f]{4}){4}[0-9a-f]{8}$'),
|
|
re.I)
|
|
|
|
b64_re = re.compile(str2u(r'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2,3})?'))
|
|
|
|
ZERO_VECTOR = Vector((0.0, 0.0, 0.0))
|
|
ZERO_ROTATION = Quaternion((0.0, 0.0, 0.0, 1.0))
|
|
NULL_KEY = u'00000000-0000-0000-0000-000000000000'
|
|
TOUCH_INVALID_TEXCOORD = Vector((-1.0, -1.0, 0.0))
|
|
|
|
Infinity = float('inf')
|
|
Indet = Infinity * 0
|
|
NaN = -Indet # Don't use float("nan") - Windows gets upset.
|
|
|
|
# Upper/Lower casing tables
|
|
lowerMap = False
|
|
upperMap = False
|
|
lowerLSOMap = False
|
|
upperLSOMap = False
|
|
|
|
class ELSLTypeMismatch(Exception):
|
|
def __init__(self):
|
|
super(ELSLTypeMismatch, self).__init__(u"Type mismatch")
|
|
|
|
class ELSLMathError(Exception):
|
|
def __init__(self):
|
|
super(ELSLMathError, self).__init__(u"Math Error")
|
|
|
|
class ELSLInvalidType(Exception):
|
|
def __init__(self):
|
|
super(ELSLInvalidType, self).__init__(u"Internal error: Invalid type")
|
|
|
|
class ELSLCantCompute(Exception):
|
|
pass
|
|
|
|
# We don't yet support the LSO string model (arbitrary zero-terminated byte
|
|
# sequences). This exception is triggered to report attempts at using it.
|
|
class ELSONotSupported(Exception):
|
|
pass
|
|
|
|
# LSL types are translated to Python types as follows:
|
|
# * LSL string -> Python unicode
|
|
# * LSL key -> Key (class derived from unicode, no significant changes except __repr__)
|
|
# * LSL integer -> Python int (should never be long)
|
|
# * LSL float -> Python float
|
|
# * LSL vector -> Vector (class derived from Python tuple) of 3 numbers (float)
|
|
# * LSL rotation -> Quaternion (class derived from Python tuple) of 4 numbers (float)
|
|
# * LSL list -> Python list
|
|
|
|
Types = {
|
|
int: 1, # TYPE_INTEGER
|
|
float: 2, # TYPE_FLOAT
|
|
unicode: 3, # TYPE_STRING
|
|
Key: 4, # TYPE_KEY
|
|
Vector: 5, # TYPE_VECTOR
|
|
Quaternion: 6, # TYPE_ROTATION
|
|
list: 0, # TYPE_INVALID
|
|
}
|
|
|
|
# Utility functions
|
|
|
|
def F32(f, f32=True):
|
|
"""Truncate a float to have a precision equivalent to IEEE single"""
|
|
|
|
if not f32: # don't truncate
|
|
return f
|
|
|
|
if isinstance(f, tuple): # vector, quaternion
|
|
return f.__class__(F32(i) for i in f)
|
|
|
|
# Alternative to the big blurb below. This relies on the machine using IEEE-754, though.
|
|
|
|
# Using array:
|
|
#from array import array
|
|
#return array('f',(f,))[0]
|
|
|
|
# Using struct:
|
|
#from struct import pack, unpack
|
|
#return unpack('f', pack('f', f))[0]
|
|
|
|
# Using numpy:
|
|
#import numpy
|
|
#return float(numpy.float32(f))
|
|
|
|
# Using ctypes:
|
|
#from ctypes import c_float
|
|
return c_float(f).value
|
|
|
|
# These are other approaches that are not fully debugged:
|
|
|
|
# This one is tested against c_float, but not carefully verified:
|
|
# if math.isnan(f) or math.isinf(f) or f == 0.0:
|
|
# return f
|
|
#
|
|
# m, x = math.frexp(abs(f))
|
|
#
|
|
# if x > 128:
|
|
# return math.copysign(Infinity, f)
|
|
#
|
|
# if x < -125:
|
|
# m = math.ldexp(m, x + 149)
|
|
# x = -125
|
|
# else:
|
|
# m = m * 0x1000000
|
|
#
|
|
# frac = m % 1
|
|
# m -= frac
|
|
# assert m.is_integer()
|
|
# m = int(m)
|
|
#
|
|
# # Round to even
|
|
# if frac > 0.5 or frac == 0.5 and (m & 1):
|
|
# m += 1
|
|
# if m == 0x1000000:
|
|
# m = 0x800000
|
|
# x += 1
|
|
#
|
|
# # re-check for overflow
|
|
# if x > 128:
|
|
# return math.copysign(Infinity, f)
|
|
#
|
|
# if m == 0:
|
|
# return math.copysign(0.0, f)
|
|
#
|
|
# return math.ldexp(math.copysign(m/16777216.0, f), x)
|
|
|
|
# # Another alternative.
|
|
# m, x = math.frexp(abs(f))
|
|
# if x > 128:
|
|
# return math.copysign(Infinity, f)
|
|
# if x < -149:
|
|
# return math.copysign(0.0, f)
|
|
# if x < -125:
|
|
# e = 1<<(x+149)
|
|
# else:
|
|
# e = 16777216.0
|
|
# # Special corner case with rounding near the maximum float (e.g. 3.4028236e38 gets rounded up, going out of range for a F32)
|
|
# if m*e >= 16777215.5 and x == 128:
|
|
# return math.copysign(Infinity, f)
|
|
# return math.ldexp(math.copysign(math.floor(m*e+0.5)/e, f), x)
|
|
|
|
|
|
# # Original old-fashioned strategy (watch out for the 16777215.5 bug above):
|
|
#
|
|
# if math.isinf(f) or math.isnan(f) or f==0:
|
|
# return f
|
|
# s = math.copysign(1, f)
|
|
# # This number may not be precise enough if Python had infinite precision, but it works for us.
|
|
# if f < 0.0000000000000000000000000000000000000000000007006492321624086132496:
|
|
# return math.copysign(0.0, s)
|
|
# f = abs(f)
|
|
#
|
|
#
|
|
# # TO DO: Check this boundary (this is 2^128)
|
|
# if f >= 340282366920938463463374607431768211456.0:
|
|
# return math.copysign(Infinity, s)
|
|
#
|
|
# # TO DO: Check this boundary (2^-126; hopefully there's some overlap and the precision can be cut)
|
|
# if f < 0.000000000000000000000000000000000000011754943508222875079687365372222456778186655567720875215087517062784172594547271728515625:
|
|
# # Denormal range
|
|
# f *= 713623846352979940529142984724747568191373312.0
|
|
# e = 0.00000000000000000000000000000000000000000000140129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125 # 2^-149
|
|
# else:
|
|
# e = 1.0
|
|
# # This first loop is an optimization to get closer to the destination faster for very small numbers
|
|
# while f < 1.0:
|
|
# f *= 16777216.0
|
|
# e *= 0.000000059604644775390625
|
|
# # Go bit by bit
|
|
# while f < 8388608.0:
|
|
# f *= 2.0
|
|
# e *= 0.5
|
|
#
|
|
# #This first loop is an optimization to get closer to the destination faster for very big numbers
|
|
# while f >= 140737488355328.0:
|
|
# f *= 0.000000059604644775390625
|
|
# e *= 16777216.0
|
|
# # Go bit by bit
|
|
# while f >= 16777216.0:
|
|
# f *= 0.5
|
|
# e *= 2.0
|
|
#
|
|
# return math.copysign(math.floor(f+0.5)*e, s)
|
|
|
|
def S32(val):
|
|
"""Return a signed integer truncated to 32 bits (must deal with longs too)"""
|
|
if -2147483648 <= val <= 2147483647:
|
|
return int(val)
|
|
val &= 0xFFFFFFFF
|
|
if val > 2147483647:
|
|
return int(val - 4294967296)
|
|
return int(val)
|
|
|
|
def zstr(s):
|
|
if not isinstance(s, unicode):
|
|
# This can only be the result of an internal error; call attention to
|
|
# it by raising ELSLInvalidType instead of ELSLTypeMismatch.
|
|
raise ELSLInvalidType
|
|
|
|
zi = s.find(u'\0')
|
|
if zi < 0:
|
|
return s
|
|
return s.__class__(s[:zi])
|
|
|
|
def fi(x):
|
|
"""Force x to be an int"""
|
|
if type(x) != int or not (-2147483648 <= x <= 2147483647):
|
|
raise ELSLTypeMismatch
|
|
return x
|
|
|
|
def ff(x):
|
|
"""Force x to be a float"""
|
|
if int != type(x) != float:
|
|
raise ELSLTypeMismatch
|
|
if type(x) != float:
|
|
return InternalTypecast(x, float, False, True)
|
|
return F32(x)
|
|
|
|
def fk(k):
|
|
"""Force k to be a key"""
|
|
if unicode != type(k) != Key:
|
|
raise ELSLTypeMismatch
|
|
if type(k) != Key:
|
|
k = InternalTypecast(k, Key, False, False)
|
|
return k
|
|
|
|
def fs(s):
|
|
"""Force s to be a string"""
|
|
if unicode != type(s) != Key:
|
|
raise ELSLTypeMismatch
|
|
if type(s) != unicode:
|
|
s = InternalTypecast(s, unicode, False, False)
|
|
return s
|
|
|
|
def fl(L):
|
|
"""Force l to be a list, and its elements to have sane types."""
|
|
Lorig = L
|
|
if type(L) != list:
|
|
raise ELSLTypeMismatch
|
|
for i in xrange(len(L)):
|
|
t = type(L[i])
|
|
if t not in Types:
|
|
raise ELSLInvalidType
|
|
if t == Vector:
|
|
# copy on write
|
|
if L is Lorig:
|
|
L = L[:]
|
|
L[i] = v2f(L[i])
|
|
if t == Quaternion:
|
|
# copy on write
|
|
if L is Lorig:
|
|
L = L[:]
|
|
L[i] = q2f(L[i])
|
|
return L
|
|
|
|
def q2f(q):
|
|
if type(q) != Quaternion:
|
|
raise ELSLTypeMismatch
|
|
if type(q[0]) == type(q[1]) == type(q[2]) == type(q[3]) == float:
|
|
return q
|
|
return Quaternion((ff(q[0]), ff(q[1]), ff(q[2]), ff(q[3])))
|
|
|
|
def v2f(v):
|
|
if type(v) != Vector:
|
|
raise ELSLTypeMismatch
|
|
if type(v[0]) == type(v[1]) == type(v[2]) == float:
|
|
return v
|
|
return Vector((ff(v[0]), ff(v[1]), ff(v[2])))
|
|
|
|
def f2s(val, DP=6):
|
|
if math.isinf(val):
|
|
return u'Infinity' if val > 0 else u'-Infinity'
|
|
if math.isnan(val):
|
|
return u'NaN'
|
|
if lslcommon.LSO or val == 0.:
|
|
return u'%.*f' % (DP, val) # deals with -0.0 too
|
|
|
|
# Format according to Mono rules. Mono displays 7 significant decimal
|
|
# digits, rounded with round-to-nearest-or-even.
|
|
# Decimal numbers seem to be subjected to an extra rounding:
|
|
# with 6 decimals output, 0.0000014999995 is rounded as 0.000002,
|
|
# while 0.0000014999994 is rounded as 0.000001. The rounding point for the
|
|
# first rounding is the 8th significant decimal, per the above; SL applies
|
|
# a second rounding at the (DP+1)-th decimal which seems to be of the
|
|
# round-to-nearest, ties-away-from-zero kind.
|
|
|
|
# First, find the decimal mantissa and exponent. The first rounding is
|
|
# derived from the conversion itself, and it applies the built-in
|
|
# round-to-nearest-or-even rounding mode.
|
|
m, e = (u'%.6e' % val).split(u'e')
|
|
# Convert the exponent to integer
|
|
e = int(e, 10)
|
|
# Separate the sign
|
|
if m[0] == u'-':
|
|
sgn = u'-'
|
|
m = m[1:]
|
|
else:
|
|
sgn = u''
|
|
|
|
# Remove the decimal point but leave the mantissa as a string; add a
|
|
# leading '0' to catch a possible carry all the way to the first digit.
|
|
m = u'0' + m[0] + m[2:]
|
|
|
|
# If the first digit is at a decimal position > DP+1, the result is zero.
|
|
# Same if it lies exactly at DP+1 and the first digit is < 5.
|
|
# Otherwise, we have to check rounding at a minimum.
|
|
i = 1 if m[0] == u'0' else 0 # determine 1st significant digit
|
|
if e < -(DP+1) or e == -(DP+1) and m[i] < u'5':
|
|
return u'0.' + u'0' * DP
|
|
|
|
# Predict where DP will trim the number, so that we can apply the second
|
|
# rounding to the digit after:
|
|
i = DP + e + 2
|
|
if 0 <= i <= 7 and m[i] >= u'5':
|
|
# Need to round up; add 1 in the right place
|
|
i = i - 1
|
|
while m[i] == u'9':
|
|
m = m[:i] + u'0' + m[i+1:]
|
|
i = i - 1
|
|
# Add 1 to the first digit found that was not a 9
|
|
# (we are guaranteed to have at least one: the initial zero)
|
|
m = m[:i] + unichr(ord(m[i]) + 1) + m[i+1:]
|
|
|
|
# If first digit is 0, remove it; if not, increase the exponent because
|
|
# the leading digit has changed, so e.g. 9.9999995e4 is now 1.000000e5.
|
|
# Also, make sure that m ends up with 7 digits.
|
|
if m[0] == u'0':
|
|
m = m[1:]
|
|
else:
|
|
e = e + 1
|
|
m = m[:7]
|
|
|
|
# Now we have the final 7 digits. Complete the number using the exponent.
|
|
if e >= 6:
|
|
m = m + u'0' * (e - 6) + u'.' + u'0' * DP
|
|
elif e < 0:
|
|
m = u'0.' + u'0' * (-1 - e) + m[:]#FIXME
|
|
else:
|
|
m = m[:e+1] + u'.' + m[e+1:] + u'0' * (e - len(m) + DP + 1)
|
|
|
|
# Cut out the decimal part at DP decimals; add sign and return the result
|
|
dotpos = m.index(u'.')
|
|
return sgn + m[:dotpos + 1 + DP]
|
|
|
|
def vr2s(v, DP=6):
|
|
assert len(v) == (3 if type(v) == Vector else 4)
|
|
return u'<' + ', '.join(f2s(x, DP) for x in v) + u'>'
|
|
|
|
def qnz(q):
|
|
return Quaternion((0.,0.,0.,1.)) if all(x == 0. for x in q) else q
|
|
|
|
def qnorm(q):
|
|
q = qnz(q)
|
|
mag2 = math.fsum((q[0]*q[0], q[1]*q[1], q[2]*q[2], q[3]*q[3]))
|
|
# Threshold for renormalization
|
|
eps_h = 1.0000021457672119140625 # float.fromhex('0x1.000024p0')
|
|
eps_l = 0.99999797344207763671875 # float.fromhex('0x1.FFFFBCp-1')
|
|
if mag2 >= eps_h or mag2 <= eps_l:
|
|
# Renormalize
|
|
mag2 = math.sqrt(mag2)
|
|
return Quaternion((q[0]/mag2, q[1]/mag2, q[2]/mag2, q[3]/mag2))
|
|
return q
|
|
|
|
def InternalTypecast(val, out, InList, f32):
|
|
"""Type cast val to out, following LSL rules.
|
|
|
|
To avoid mutual recursion, it deals with everything except lists. That way
|
|
it does not need to call InternalList2Strings which needs to call it.
|
|
"""
|
|
tval = type(val)
|
|
# The case tval == list is handled in typecast() below.
|
|
if out == list:
|
|
return [val]
|
|
|
|
if tval == int: # integer
|
|
val = S32(val)
|
|
if out == int: return val
|
|
if out == float: return F32(val, f32)
|
|
if out == unicode: return unicode(val)
|
|
raise ELSLTypeMismatch
|
|
|
|
if tval == float:
|
|
val = F32(val, f32)
|
|
if out == int:
|
|
return (S32(int(val)) if -2147483648.0 <= val < 2147483648.0
|
|
else -2147483648)
|
|
if out == float: return val
|
|
if out == unicode: return f2s(val, 6)
|
|
raise ELSLTypeMismatch
|
|
|
|
if tval == Vector:
|
|
val = v2f(val)
|
|
if out == Vector: return val
|
|
if out == unicode: return vr2s(val, 6 if InList else 5)
|
|
raise ELSLTypeMismatch
|
|
if tval == Quaternion:
|
|
val = q2f(val)
|
|
if out == Quaternion: return val
|
|
if out == unicode: return vr2s(val, 6 if InList else 5)
|
|
raise ELSLTypeMismatch
|
|
if tval == Key: # key
|
|
if out == Key: return zstr(val)
|
|
if out == unicode: return zstr(unicode(val))
|
|
raise ELSLTypeMismatch
|
|
|
|
if tval == unicode:
|
|
val = zstr(val)
|
|
if out == unicode: return val
|
|
if out == Key: return Key(val)
|
|
if out == float:
|
|
# Clean up the string for Picky Python
|
|
match = float_re.search(val)
|
|
if match is None:
|
|
return 0.0
|
|
if match.group(1):
|
|
ret = float.fromhex(match.group(0))
|
|
# The following is no longer true as of Server 2022-05-05.571557;
|
|
# exact date of change is unknown.
|
|
# (float)"-nan" returns -nan now.
|
|
#elif match.group(2):
|
|
# # (float)"-nan" produces NaN instead of Indet, even though
|
|
# # (vector)"<-nan,0,0>" produces <Indet, 0., 0.>. Go figure.
|
|
# ret = NaN
|
|
elif match.group(2):
|
|
ret = Indet if match.group(0)[0] == '-' else NaN
|
|
else:
|
|
ret = float(match.group(0))
|
|
# The following is no longer true as of Server 2022-05-05.571557;
|
|
# exact date of change is unknown.
|
|
# Thanks to SaladDais@users.noreply.github.com for noticing.
|
|
# Note that if the code needs to be resurrected, the sign needs
|
|
# to be verified for the case (float)"-0.0"; we can no longer
|
|
# check how it worked before.
|
|
#if not lslcommon.LSO and abs(ret) < 1.1754943157898259e-38:
|
|
# # Mono doesn't return denormals when using (float)"val"
|
|
# # (but it returns them when using (vector)"<val,...>")
|
|
# ret = 0.0
|
|
return F32(ret, f32)
|
|
if out == int:
|
|
match = int_re.search(val)
|
|
if match is None:
|
|
return 0
|
|
val = match.group(0)
|
|
if match.group(1):
|
|
val = int(val, 0)
|
|
else:
|
|
val = int(val)
|
|
if -4294967295 <= val <= 4294967295:
|
|
return S32(val)
|
|
return -1
|
|
if out in (Vector, Quaternion):
|
|
Z,dim = (ZERO_VECTOR,3) if out == Vector else (ZERO_ROTATION,4)
|
|
ret = []
|
|
if not val.startswith(u'<'):
|
|
return Z
|
|
val = val[1:]
|
|
for _ in range(dim):
|
|
match = vfloat_re.search(val)
|
|
if match is None:
|
|
return Z
|
|
if match.group(1):
|
|
ret.append(F32(float.fromhex(match.group(0)), f32))
|
|
elif match.group(2):
|
|
ret.append(Indet if match.group(0).startswith(u'-') else
|
|
NaN)
|
|
else:
|
|
ret.append(F32(float(match.group(0)), f32))
|
|
if len(ret) < dim:
|
|
i = match.end()
|
|
if val[i:i+1] != u',':
|
|
return Z
|
|
val = val[i+1:]
|
|
return out(ret) # convert type
|
|
|
|
# To avoid mutual recursion, this was moved:
|
|
#if tval == list: # etc.
|
|
|
|
raise ELSLInvalidType
|
|
|
|
def fpz(f):
|
|
"""Like f2s but forcing positive zero."""
|
|
ret = f2s(f)
|
|
return ret if ret != u'-0.000000' else u'0.000000'
|
|
|
|
def InternalList2Strings(val, ForcePositiveZero=False):
|
|
"""Convert a list of misc.items to a list of strings."""
|
|
ret = []
|
|
if ForcePositiveZero:
|
|
for elem in val:
|
|
telem = type(elem)
|
|
if telem == unicode:
|
|
ret.append(zstr(elem))
|
|
elif telem == int:
|
|
ret.append(unicode(elem))
|
|
elif telem == Key:
|
|
ret.append(zstr(unicode(elem)))
|
|
elif telem == float:
|
|
ret.append(fpz(F32(elem)))
|
|
else: # Vector or Quaternion
|
|
ret.append(u'<' + u', '.join(fpz(F32(f)) for f in elem) + u'>')
|
|
else:
|
|
for elem in val:
|
|
ret.append(InternalTypecast(elem, unicode, InList=True, f32=True))
|
|
return ret
|
|
|
|
good_utf8_re = re.compile(b'(?:'
|
|
b'[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]'
|
|
b'|[\xE1-\xEC\xEE][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}'
|
|
b'|\xE0[\xA0-\xBF][\x80-\xBF]|\xED[\x80-\x9F][\x80-\xBF]'
|
|
b'|\xEF[\x80-\xBE][\x80-\xBF]|\xEF\xBF[\x80-\xBD\xBF]'
|
|
b'|\xF0[\x90-\xBF][\x80-\xBF]{2}|\xF4[\x80-\x8F][\x80-\xBF]{2}'
|
|
b')+')
|
|
|
|
def InternalUTF8toString(s):
|
|
"""Convert UTF-8 to a Unicode string, marking invalid UTF-8 with ?'s."""
|
|
# Note Mono and LSO behave differently here.
|
|
# LSO *CAN* store invalid UTF-8.
|
|
# For example, llEscapeURL(llUnescapeURL("%80%C3")) gives "%80%C3" in LSO.
|
|
# (But llEscapeURL(llUnescapeURL("%80%00%C3")) still gives "%80")
|
|
# We don't emulate it, we've built this with Unicode strings in mind.
|
|
|
|
# decode(..., 'replace') replaces invalid chars with U+FFFD which is not
|
|
# what LSL does (LSL replaces with '?'). Since U+FFFD must be preserved if
|
|
# present, we need to write our own algorithm.
|
|
|
|
# We reproduce the following observed behaviour:
|
|
# - A valid UTF-8 sequence is a RFC-2279 UTF-8 sequence excluding overlong
|
|
# sequences, the range U+D800-U+DFFF (UTF-16 surrogate pairs), everything
|
|
# above U+10FFFF and the codepoint U+FFFE. Any sequence that is not valid
|
|
# is invalid.
|
|
# - If an invalid sequence is detected, each byte in the sequence is
|
|
# treated as an invalid character and replaced with a ? character.
|
|
#
|
|
# The invalid sequences are the ones starting with the following:
|
|
# - \xC0 and \xC1 generate overlong codes in the range 0-7F.
|
|
# - \xE0\x80 through \xE0\x9F generate overlong codes in the range 0-7FF.
|
|
# - \xED\xA0 through \xED\xBF generate high and low UTF-16 surrogates.
|
|
# - \xEF\xBF\xBE is the sequence for the invalid code U+FFFE.
|
|
# - \xF0\x80 through \xF0\x8F generate overlong codes in the range 0-FFFF.
|
|
# - \xF4\x90 through \xF4\xBF generate codes above U+10FFFF.
|
|
# - \xF5 through \xFD generate even bigger codes.
|
|
# - \xFE and \xFF were never valid UTF-8.
|
|
#
|
|
# Reminder: Start codes \xC0-\xDF have length 2; \xE0-\xEF have length 3
|
|
# and \xF0-\xF4, length 4.
|
|
#
|
|
# Examples:
|
|
# b'\xC0\x81' is invalid because it represents an overlong U+0001.
|
|
# It should be replaced with u'??'.
|
|
# b'\xED\xA0\x80' is invalid because it represents the UTF-16 upper
|
|
# surrogate. It should be replaced with u'???'.
|
|
# b'\x80\x81\xFF' is invalid because it does not represent anything
|
|
# resembling UTF-8. It should be replaced with u'???'.
|
|
# b'\xF4\xC3\x80' is partially invalid and partially valid, and should
|
|
# be replaced with u'?\U000000C0'.
|
|
|
|
ret = u''
|
|
last = 0
|
|
invalid_sum = 0
|
|
for frag in good_utf8_re.finditer(s):
|
|
invalid_length = frag.start() - last
|
|
ret += u'?' * invalid_length
|
|
ret += frag.group().decode('utf8')
|
|
last = frag.end()
|
|
invalid_sum += invalid_length
|
|
invalid_length = len(s) - last
|
|
ret += u'?' * invalid_length
|
|
invalid_sum += invalid_length
|
|
if invalid_sum and lslcommon.LSO:
|
|
raise ELSONotSupported(u"Byte strings not supported")
|
|
return zstr(ret)
|
|
|
|
# The code of llDeleteSubList and llDeleteSubString is identical except for the
|
|
# type check. Same for llGetSubString and llList2List. They are all joined into
|
|
# one single function.
|
|
def InternalGetDeleteSubSequence(val, start, end, isGet):
|
|
if type(val) == unicode:
|
|
val = uniwrap(val)
|
|
start = fi(start)
|
|
end = fi(end)
|
|
L = len(val)
|
|
|
|
# Python does much of the same thing as LSL here, which helps a lot
|
|
if end == -1: end += L
|
|
if (start+L if start < 0 else start) > (end+L if end < 0 else end):
|
|
# Exclusion range - get/delete from end and start
|
|
return val[:end+1] + val[start:] if isGet else val[end+1:start]
|
|
return val[start:end+1] if isGet else val[:start] + val[end+1:]
|
|
|
|
def InternalCompareElems(e1, e2):
|
|
"""Compares two list elements according to llListFindList rules."""
|
|
# NaN is found in floats, but not in vectors
|
|
if type(e1) == type(e2) == float:
|
|
if e1 == e2:
|
|
return True
|
|
# Exceptionally, NaN equals NaN
|
|
if math.isnan(e1) and math.isnan(e2):
|
|
return True
|
|
# Mismatch
|
|
return False
|
|
elif type(e1) == type(e2) in (Vector, Quaternion):
|
|
# Act as if the list's vector/quat was all floats, even if not
|
|
if type(e1) == Vector:
|
|
e1 = v2f(e1)
|
|
e2 = v2f(e2)
|
|
else:
|
|
e1 = q2f(e1)
|
|
e2 = q2f(e2)
|
|
# Unfortunately, Python fails to consider (NaN,) != (NaN,) sometimes
|
|
# so we need to implement our own test
|
|
for e1e,e2e in zip(e1,e2):
|
|
# NaNs are considered different to themselves here as normal
|
|
if e1e != e2e:
|
|
# Mismatch in vector/quaternion component
|
|
break
|
|
else:
|
|
# No mismatch in any component
|
|
return True
|
|
# Mismatch
|
|
return False
|
|
elif type(e1) != type(e2) or e1 != e2:
|
|
return False
|
|
return True
|
|
|
|
def InternalListFindList(lst, elems, start, end, stride, instance):
|
|
"""Generalizes llListFindList with start/end, stride and instance args."""
|
|
L1 = len(lst)
|
|
L2 = len(elems)
|
|
if L1 == L2 == 0:
|
|
return 0 # an empty list is always found in an empty list
|
|
if L2 > L1:
|
|
return -1 # can't find a sublist longer than the original list
|
|
if start < 0:
|
|
start += L1
|
|
if end < 0:
|
|
end += L1
|
|
if end >= L1:
|
|
end = L1 - 1
|
|
if start < 0 or start >= L1 or end < 0 or end >= L1 or start > end:
|
|
return -1
|
|
if L2 == 0:
|
|
# empty list is always found at position 0
|
|
return 0
|
|
if stride < 1:
|
|
return -1 # stride 0 or negative returns -1
|
|
range = xrange(start, end+2-L2, stride) if instance >= 0 else xrange(
|
|
end+1-L2, start-1, -stride)
|
|
for i in range:
|
|
for j in xrange(L2):
|
|
if not InternalCompareElems(lst[i+j], elems[j]):
|
|
break # mismatch
|
|
else:
|
|
# no mismatch
|
|
if instance < 0:
|
|
instance += 1
|
|
if instance == 0:
|
|
return i
|
|
if instance > 0:
|
|
instance -= 1
|
|
return -1
|
|
|
|
def reduce(t):
|
|
t = F32(t)
|
|
if not t.is_integer():
|
|
return t # Accurate-ish until big numbers come into play
|
|
return int(t * 18446744073709551616) % 115904311329233965478 / 18446744073709551616.
|
|
|
|
#
|
|
# Functions for LSL-compatible operators
|
|
#
|
|
|
|
def typecast(val, out, InList=False, f32=True):
|
|
"""Type cast an item. Calls InternalList2Strings for lists and
|
|
defers the rest to InternalTypecast.
|
|
"""
|
|
if type(val) == list:
|
|
if out == list:
|
|
return val # NOTE: We're not duplicating it here.
|
|
if out == unicode:
|
|
return u''.join(InternalList2Strings(val))
|
|
raise ELSLTypeMismatch
|
|
return InternalTypecast(val, out, InList, f32)
|
|
|
|
def neg(val):
|
|
if type(val) in (int, float):
|
|
if type(val) == int and val == -2147483648:
|
|
return val
|
|
return -val
|
|
if isinstance(val, tuple):
|
|
return val.__class__(-f for f in val)
|
|
raise ELSLTypeMismatch
|
|
|
|
def add(a, b, f32=True):
|
|
# defined for:
|
|
# scalar+scalar
|
|
# vector+vector
|
|
# rotation+rotation
|
|
# string+string
|
|
# (our extension:) key+string, string+key
|
|
# list+any
|
|
# any+list
|
|
ta=type(a)
|
|
tb=type(b)
|
|
if ta in (int, float) and tb in (int, float):
|
|
if ta == tb == int:
|
|
return S32(a+b)
|
|
return F32(ff(a)+ff(b), f32)
|
|
|
|
if ta == tb in (list, unicode):
|
|
return a + b
|
|
# string + key, key + string are allowed here
|
|
if ta in (unicode, Key) and tb in (unicode, Key) and not (ta == tb == Key):
|
|
return a + b
|
|
if ta == list:
|
|
return a + [b]
|
|
if tb == list:
|
|
return [a] + b
|
|
if ta == tb in (Vector, Quaternion):
|
|
return F32(ta(ff(a[i])+ff(b[i]) for i in range(len(a))), f32)
|
|
raise ELSLTypeMismatch
|
|
|
|
def sub(a, b, f32=True):
|
|
# defined for:
|
|
# scalar+scalar
|
|
# vector+vector
|
|
# rotation+rotation
|
|
ta=type(a)
|
|
tb=type(b)
|
|
if ta in (int, float) and tb in (int, float):
|
|
if ta == tb == int:
|
|
return S32(a-b)
|
|
return F32(ff(a)-ff(b), f32)
|
|
if ta == tb in (Vector, Quaternion):
|
|
return F32(ta(ff(a[i])-ff(b[i]) for i in range(len(a))), f32)
|
|
raise ELSLTypeMismatch
|
|
|
|
def mul(a, b, f32=True):
|
|
# defined for:
|
|
# scalar*scalar
|
|
# scalar*vector
|
|
# vector*scalar
|
|
# vector*vector
|
|
# vector*rotation
|
|
# rotation*rotation
|
|
ta = type(a)
|
|
tb = type(b)
|
|
# If either type is string, list, or key, error
|
|
if ta in (unicode, list, Key) or tb in (unicode, list, Key):
|
|
raise ELSLTypeMismatch
|
|
# only int, float, vector, quaternion here
|
|
if ta in (int, float):
|
|
if tb in (int, float):
|
|
if ta == tb == int:
|
|
return S32(a*b)
|
|
if math.isnan(a) and math.isnan(b):
|
|
return (-NaN
|
|
if math.copysign(1, a) == math.copysign(1, b) == -1
|
|
else NaN)
|
|
return F32(ff(a)*ff(b), f32)
|
|
if tb != Vector:
|
|
# scalar * quat is not defined
|
|
raise ELSLTypeMismatch
|
|
# scalar * vector
|
|
a, ta, b, tb = b, tb, a, ta # turn into vector * scalar
|
|
|
|
if ta == Quaternion:
|
|
# quat * scalar and quat * vector are not defined
|
|
if tb != Quaternion:
|
|
raise ELSLTypeMismatch
|
|
a = q2f(a)
|
|
b = q2f(b)
|
|
# quaternion product - product formula reversed
|
|
return Quaternion(F32((F32(a[0] * b[3]) + F32(a[3] * b[0]) + F32(a[2] * b[1]) - F32(a[1] * b[2]),
|
|
F32(a[1] * b[3]) - F32(a[2] * b[0]) + F32(a[3] * b[1]) + F32(a[0] * b[2]),
|
|
F32(a[2] * b[3]) + F32(a[1] * b[0]) - F32(a[0] * b[1]) + F32(a[3] * b[2]),
|
|
F32(a[3] * b[3]) - F32(a[0] * b[0]) - F32(a[1] * b[1]) - F32(a[2] * b[2])
|
|
), f32))
|
|
|
|
if ta != Vector:
|
|
raise ELSLInvalidType # Should never happen at this point
|
|
|
|
if tb in (int, float):
|
|
a = v2f(a)
|
|
b = ff(b)
|
|
return Vector(F32((mul(a[0], b), mul(a[1], b), mul(a[2], b)), f32))
|
|
|
|
if tb == Vector:
|
|
# scalar product
|
|
a = v2f(a)
|
|
b = v2f(b)
|
|
return F32(math.fsum((a[0]*b[0], a[1]*b[1], a[2]*b[2])), f32)
|
|
|
|
if tb != Quaternion:
|
|
raise ELSLInvalidType # Should never happen at this point
|
|
|
|
# vector * quaternion: perform conjugation
|
|
#v = mul(Quaternion((-b[0], -b[1], -b[2], b[3])),
|
|
# mul(Quaternion((a[0], a[1], a[2], 0.0)), b, f32=False))
|
|
#return Vector((v[0], v[1], v[2]))
|
|
# this removes redundant calculations:
|
|
a = v2f(a)
|
|
b = q2f(b)
|
|
b0b0 = b[0] * b[0]
|
|
b0b1 = b[0] * b[1]
|
|
b0b2 = b[0] * b[2]
|
|
b0b3 = b[0] * b[3]
|
|
b1b1 = b[1] * b[1]
|
|
b1b2 = b[1] * b[2]
|
|
b1b3 = b[1] * b[3]
|
|
b2b2 = b[2] * b[2]
|
|
b2b3 = b[2] * b[3]
|
|
b3b3 = b[3] * b[3]
|
|
return Vector(F32(
|
|
( a[0] * (b0b0 - b1b1 - b2b2 + b3b3)
|
|
+ a[1] * (b0b1 - b2b3) * 2
|
|
+ a[2] * (b0b2 + b1b3) * 2
|
|
, a[0] * (b0b1 + b2b3) * 2
|
|
+ a[1] * (b1b1 - b0b0 - b2b2 + b3b3)
|
|
+ a[2] * (b1b2 - b0b3) * 2
|
|
, a[0] * (b0b2 - b1b3) * 2
|
|
+ a[1] * (b1b2 + b0b3) * 2
|
|
+ a[2] * (b2b2 - b0b0 - b1b1 + b3b3)
|
|
), f32))
|
|
|
|
def div(a, b, f32=True):
|
|
# defined for:
|
|
# scalar/scalar
|
|
# vector/scalar
|
|
# vector/rotation
|
|
# rotation/rotation
|
|
ta = type(a)
|
|
tb = type(b)
|
|
if tb in (int, float):
|
|
if b == 0:
|
|
raise ELSLMathError
|
|
if ta in (int, float):
|
|
if ta == int and tb == int:
|
|
# special case
|
|
if a == -2147483648 and b == -1:
|
|
return a # this could be handled by using S32 but it's probably faster this way
|
|
if (a < 0) ^ (b < 0):
|
|
# signs differ - Python rounds towards -inf, we need rounding towards 0
|
|
return -(a//-b)
|
|
return a//b
|
|
ret = F32(ff(a)/ff(b), f32)
|
|
if math.isnan(ret): # A NaN result gives a math error.
|
|
raise ELSLMathError
|
|
return ret
|
|
if ta == Vector:
|
|
a = v2f(a)
|
|
b = ff(b)
|
|
return Vector(F32(tuple(NaN if math.isnan(x) and math.isnan(b)
|
|
and math.copysign(1, x)
|
|
!= math.copysign(1, b)
|
|
else x/b
|
|
for x in a), 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, 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))
|
|
if type(a) == type(b) == Vector:
|
|
# cross product
|
|
a = v2f(a)
|
|
b = v2f(b)
|
|
return Vector(F32((a[1]*b[2]-a[2]*b[1],
|
|
a[2]*b[0]-a[0]*b[2],
|
|
a[0]*b[1]-a[1]*b[0]), f32))
|
|
|
|
raise ELSLTypeMismatch
|
|
|
|
def compare(a, b, Eq = True):
|
|
"""Calculate a == b when Eq is True, or a != b when not"""
|
|
|
|
# Defined for all types as long as one of them can be auto-cast to the other
|
|
ta = type(a)
|
|
tb = type(b)
|
|
if ta in (int, float) and tb in (int, float):
|
|
# we trust that NaN == NaN is False
|
|
if ta == tb == int:
|
|
ret = a == b
|
|
else:
|
|
ret = ff(a) == ff(b)
|
|
return int(ret == Eq)
|
|
if ta in (unicode, Key) and tb in (unicode, Key):
|
|
ret = 0 if a == b else 1 if (not lslcommon.LSO
|
|
or a.encode('utf8') > b.encode('utf8')) else -1
|
|
return int(not ret) if Eq else ret
|
|
if ta == tb in (Vector, Quaternion):
|
|
ret = not any(ae != be for ae, be in zip(a, b))
|
|
return int(ret == Eq)
|
|
if ta == tb == list:
|
|
ret = len(a) - len(b)
|
|
return int(not ret) if Eq else ret
|
|
raise ELSLTypeMismatch
|
|
|
|
def less(a, b):
|
|
"""Calculate a < b. The rest can be derived by swapping components and by
|
|
negating: a > b is less(b,a); a <= b is 1-less(b,a); a >= b is 1-less(a,b).
|
|
"""
|
|
if type(a) == type(b) == int:
|
|
return int(a < b)
|
|
if type(a) in (int, float) and type(b) in (int, float):
|
|
return int(ff(a) < ff(b))
|
|
raise ELSLTypeMismatch
|
|
|
|
def cond(x):
|
|
"""Test whether x evaluates to True in a condition (if, while, for, ...)"""
|
|
tx = type(x)
|
|
if tx not in Types:
|
|
raise ELSLInvalidType
|
|
if tx == Key:
|
|
if x == NULL_KEY or len(x) != 36:
|
|
return False
|
|
return bool(key_re.search(x))
|
|
if tx == Vector:
|
|
return bool(compare(x, ZERO_VECTOR, Eq=False))
|
|
if tx == Quaternion:
|
|
return bool(compare(x, ZERO_ROTATION, Eq=False))
|
|
if lslcommon.LSO and tx == list:
|
|
# SVC-689: lists of 1 element count as false
|
|
return len(x) > 1
|
|
return bool(x) # works fine for int, float, string, list
|
|
|
|
#
|
|
# LSL-compatible computation functions
|
|
#
|
|
|
|
def llAbs(i):
|
|
i = fi(i)
|
|
if i != -2147483648:
|
|
return abs(i)
|
|
return i
|
|
|
|
def llAcos(f):
|
|
f = ff(f)
|
|
try:
|
|
return F32(math.acos(f)) if not math.isnan(f) else f
|
|
except ValueError:
|
|
return NaN
|
|
|
|
def llAngleBetween(r1, r2):
|
|
r1 = q2f(r1)
|
|
r2 = q2f(r2)
|
|
return llRot2Angle(div(qnz(r1), qnz(r2), f32=False))
|
|
|
|
def llAsin(f):
|
|
f = ff(f)
|
|
try:
|
|
return F32(math.asin(f)) if not math.isnan(f) else f
|
|
except ValueError:
|
|
return NaN
|
|
|
|
def llAtan2(y, x):
|
|
y = ff(y)
|
|
x = ff(x)
|
|
if math.isnan(x) and math.isnan(y):
|
|
return mul(x, y)
|
|
if math.isnan(x):
|
|
return x
|
|
if math.isnan(y):
|
|
return y
|
|
return F32(math.atan2(y, x))
|
|
|
|
def llAxes2Rot(fwd, left, up):
|
|
fwd = v2f(fwd)
|
|
left = v2f(left)
|
|
up = v2f(up)
|
|
|
|
# One of the hardest.
|
|
|
|
t = math.fsum((fwd[0], left[1], up[2]))
|
|
if t > 0.: # no danger of division by zero or negative roots
|
|
r = math.sqrt(1. + t)
|
|
s = 0.5/r
|
|
|
|
# For the case of ix+jy+kz > 0, it can return an unnormalized quaternion
|
|
return Quaternion(F32((s*(left[2]-up[1]), s*(up[0]-fwd[2]), s*(fwd[1]-left[0]), r*0.5)))
|
|
|
|
# Find a positive combo. LSL normalizes the result in these cases only, so we do the same.
|
|
|
|
if left[1] <= fwd[0] >= up[2]: # is fwd[0] the greatest?
|
|
r = math.sqrt(1. + fwd[0] - left[1] - up[2])
|
|
s = 0.5/r
|
|
q = (r*0.5, s*(fwd[1]+left[0]), s*(up[0]+fwd[2]), s*(left[2]-up[1]))
|
|
|
|
elif fwd[0] <= left[1] >= up[2]: # is left[1] the greatest?
|
|
r = math.sqrt(1. - fwd[0] + left[1] - up[2])
|
|
s = 0.5/r
|
|
q = (s*(fwd[1]+left[0]), r*0.5, s*(left[2]+up[1]), s*(up[0]-fwd[2]))
|
|
|
|
else:
|
|
# Only one case remaining: up[2] is the greatest
|
|
r = math.sqrt(1. - fwd[0] - left[1] + up[2])
|
|
s = 0.5/r
|
|
q = (s*(up[0]+fwd[2]), s*(left[2]+up[1]), r*0.5, s*(fwd[1]-left[0]))
|
|
|
|
# Normalize
|
|
q = qnz(q)
|
|
mag = math.sqrt(math.fsum((q[0]*q[0], q[1]*q[1], q[2]*q[2], q[3]*q[3])))
|
|
return Quaternion(F32((q[0]/mag, q[1]/mag, q[2]/mag, q[3]/mag)))
|
|
|
|
def llAxisAngle2Rot(axis, angle):
|
|
axis = v2f(axis)
|
|
angle = ff(angle)
|
|
axis = llVecNorm(axis, f32=False)
|
|
if axis == ZERO_VECTOR:
|
|
angle = 0.
|
|
c = math.cos(angle*0.5)
|
|
s = math.sin(angle*0.5)
|
|
return Quaternion(F32((axis[0]*s, axis[1]*s, axis[2]*s, c)))
|
|
|
|
def llBase64ToInteger(s):
|
|
s = fs(s)
|
|
if len(s) > 8:
|
|
return 0
|
|
s = b64_re.search(s).group()
|
|
i = len(s)
|
|
s = b64decode(s + u'='*(-i & 3))
|
|
s = bytearray(s + b'\0\0\0\0')[:4]
|
|
i = s[0] if s[0] < 128 else s[0]-256
|
|
return (i<<24)+(s[1]<<16)+(s[2]<<8)+s[3]
|
|
|
|
b64tostr_re = re.compile(
|
|
b'('
|
|
# Those pass through and are caught by InternalUTF8toString:
|
|
b'\x00$' # NUL at last position (zstr removes it)
|
|
b'|[\x09\x0A\x0F\x1F-\x7F\xFE\xFF]|[\xC2-\xDF][\x80-\xBF]'
|
|
b'|(?:\xE0[\xA0-\xBF]|[\xE1-\xEF][\x80-\xBF])[\x80-\xBF]'
|
|
b'|(?:\xF0[\x90-\xBF]|[\xF1-\xF7][\x80-\xBF])[\x80-\xBF]{2}'
|
|
b'|(?:\xF8[\x88-\xBF]|[\xF9-\xFB][\x80-\xBF])[\x80-\xBF]{3}'
|
|
b'|(?:\xFC[\x84-\xBF]|\xFD[\x80-\xBF])[\x80-\xBF]{4}'
|
|
b')|('
|
|
# Those are caught here and substituted by a single "?"
|
|
# (greediness is important here):
|
|
b'[\x00-\x1F\x80-\xBF]'
|
|
b'|[\xC0-\xDF][\x80-\xBF]?'
|
|
b'|[\xE0-\xEF][\x80-\xBF]{0,2}'
|
|
b'|[\xF0-\xF7][\x80-\xBF]{0,3}'
|
|
b'|[\xF8-\xFB][\x80-\xBF]{0,4}'
|
|
b'|[\xFC-\xFD][\x80-\xBF]{0,5}'
|
|
b')|(.)' # should never be reached
|
|
)
|
|
|
|
def llBase64ToString(s):
|
|
s = fs(s)
|
|
s = b64_re.search(s).group(0)
|
|
|
|
# llUnescapeURL and llBase64ToString behave differently.
|
|
# llBase64ToString does a first check on the UTF-8 before the standard
|
|
# conversion, unlike llUnescapeURL. That makes it have a much more similar
|
|
# behaviour to LSO's than llUnescapeURL does. But LL being LL, the check
|
|
# is, of course, flawed, and some illegal sequences pass as good (but in
|
|
# Mono they are fortunately stopped on the conversion to UTF-8 instead).
|
|
# The check that llBase64ToString does has the quirk that the invalid
|
|
# sequences that it catches are treated as 1 single bad character instead
|
|
# of as many as the invalid sequence has, as normal conversion to UTF-8
|
|
# does. This causes inconsistencies in the number of ?'s returned.
|
|
|
|
# In llBase64ToString, trailing NUL is stripped, and embedded NULs are
|
|
# converted to "?". In addition, characters in range 00-1F are also
|
|
# converted to "?" except for \x09, \x0A, \x0F, \x1F.
|
|
|
|
byteseq = bytearray(b64decode(s + u'=' * (-len(s) & 3)))
|
|
|
|
pos = 0
|
|
match = b64tostr_re.search(byteseq, pos)
|
|
while match is not None:
|
|
assert match.group(3) is None, 'Fail in b64tostr_re: ' + match.group(3)
|
|
L = len(match.group(2) or '')
|
|
if L:
|
|
byteseq[pos:pos+L] = b'?'
|
|
pos = match.end(2) - L + 1
|
|
else:
|
|
pos = match.end(1)
|
|
|
|
match = b64tostr_re.search(byteseq, pos)
|
|
|
|
return InternalUTF8toString(byteseq)
|
|
|
|
def llCSV2List(s):
|
|
s = fs(s)
|
|
|
|
bracketlevel = 0
|
|
lastwascomma = True # first space is eaten!!!
|
|
lastidx = 0
|
|
i = 0
|
|
ret = []
|
|
for c in s:
|
|
if bracketlevel:
|
|
# ignore ',', focus on nesting level
|
|
if c == u'<':
|
|
bracketlevel += 1
|
|
elif c == u'>':
|
|
bracketlevel -= 1
|
|
elif lastwascomma and c == u' ': # eat space after comma
|
|
lastwascomma = False
|
|
lastidx = i+1
|
|
else:
|
|
lastwascomma = False
|
|
if c == u',':
|
|
lastwascomma = True
|
|
ret.append(s[lastidx:i])
|
|
lastidx = i+1
|
|
elif c == u'<':
|
|
bracketlevel += 1
|
|
i += 1
|
|
ret.append(s[lastidx:i])
|
|
return ret
|
|
|
|
def llCeil(f):
|
|
f = ff(f)
|
|
if math.isnan(f) or math.isinf(f) or f >= 2147483648.0 or f < -2147483648.0:
|
|
return -2147483648
|
|
return int(math.ceil(f))
|
|
|
|
def llChar(code):
|
|
code = fi(code)
|
|
|
|
if (not 1 <= code <= 0x10FFFF or 0xD800 <= code <= 0xDFFF
|
|
or code == 0xFFFE or code == 0xFFFF):
|
|
return u'' if code == 0 else u'\uFFFD'
|
|
return unichr(code)
|
|
|
|
def InternalGetAlg(alg):
|
|
hash = None
|
|
if alg == u'md5':
|
|
hash = hashlib.md5()
|
|
elif alg == u'sha1':
|
|
hash = hashlib.sha1()
|
|
elif alg == u'sha224':
|
|
hash = hashlib.sha224()
|
|
elif alg == u'sha256':
|
|
hash = hashlib.sha256()
|
|
elif alg == u'sha384':
|
|
hash = hashlib.sha384()
|
|
elif alg == u'sha512':
|
|
hash = hashlib.sha512()
|
|
return hash
|
|
|
|
def llComputeHash(data, alg):
|
|
data = bytewrap(uniwrap(fs(data)).encode('utf8'))
|
|
alg = fs(alg)
|
|
hash = InternalGetAlg(alg if alg != u'md5_sha1' else u'md5')
|
|
if hash is None:
|
|
raise ELSLCantCompute # spews error
|
|
hash.update(data)
|
|
ret = hash.hexdigest()
|
|
if alg == u'md5_sha1':
|
|
# md5_sha1 consists of concatenating the MD5 and the SHA-1
|
|
hash = InternalGetAlg(u'sha1')
|
|
hash.update(data)
|
|
ret += hash.hexdigest()
|
|
return str2u(ret)
|
|
|
|
def llCos(f):
|
|
f = ff(f)
|
|
if math.isinf(f):
|
|
return Indet
|
|
if -9223372036854775808.0 < f < 9223372036854775808.0:
|
|
return F32(math.cos(reduce(f)))
|
|
return f
|
|
|
|
def llDeleteSubList(lst, start, end):
|
|
# This acts as llList2List if there's wraparound
|
|
lst = fl(lst)
|
|
return InternalGetDeleteSubSequence(lst, start, end, isGet=False)
|
|
|
|
def llDeleteSubString(s, start, end):
|
|
# This acts as llGetSubString if there's wraparound
|
|
s = fs(s)
|
|
return InternalGetDeleteSubSequence(s, start, end, isGet=False)
|
|
|
|
def llDumpList2String(lst, sep):
|
|
lst = fl(lst)
|
|
sep = fs(sep)
|
|
return sep.join(InternalList2Strings(lst, ForcePositiveZero=not
|
|
lslcommon.LSO))
|
|
|
|
def llEscapeURL(s):
|
|
s = fs(s)
|
|
s = bytearray(s.encode('utf8'))
|
|
ret = u''
|
|
for c in s:
|
|
# 0x30='0', 0x39='9', 0x41='A', 0x5A='Z', 0x61='a', 0x7A='z'
|
|
if 0x30 <= c <= 0x39 or 0x41 <= c <= 0x5A or 0x61 <= c <= 0x7A:
|
|
ret += unichr(c)
|
|
else:
|
|
ret += u'%%%02X' % c
|
|
return ret
|
|
|
|
def llEuler2Rot(v):
|
|
v = v2f(v)
|
|
c0 = math.cos(v[0]*0.5)
|
|
s0 = math.sin(v[0]*0.5)
|
|
c1 = math.cos(v[1]*0.5)
|
|
s1 = math.sin(v[1]*0.5)
|
|
c2 = math.cos(v[2]*0.5)
|
|
s2 = math.sin(v[2]*0.5)
|
|
|
|
r = F32((s0 * c1 * c2 + c0 * s1 * s2,
|
|
c0 * s1 * c2 - s0 * c1 * s2,
|
|
c0 * c1 * s2 + s0 * s1 * c2,
|
|
c0 * c1 * c2 - s0 * s1 * s2))
|
|
|
|
# Fix the sign
|
|
c0 = math.cos(v[0])
|
|
s0 = math.sin(v[0])
|
|
c1 = math.cos(v[1])
|
|
s1 = math.sin(v[1])
|
|
c2 = math.cos(v[2])
|
|
s2 = math.sin(v[2])
|
|
d1 = c1*c2
|
|
d2 = c0*c2 - s0*s1*s2
|
|
d3 = c0*c1
|
|
if d1 + d2 + d3 > 0:
|
|
return Quaternion(-f for f in r) if r[3] < 0 else Quaternion(r)
|
|
i = 0
|
|
if d2 > d1:
|
|
i = 1
|
|
if d1 < d3 > d2:
|
|
i = 2
|
|
return Quaternion(-f for f in r) if r[i] < 0 else Quaternion(r)
|
|
|
|
def llFabs(f):
|
|
f = ff(f)
|
|
if f == 0.0 or math.isnan(f): # llFabs(-0.0) is -0.0; llFabs(-nan) is -nan
|
|
return f
|
|
return math.fabs(f)
|
|
|
|
def llFloor(f):
|
|
f = ff(f)
|
|
if math.isnan(f) or math.isinf(f) or f >= 2147483648.0 or f < -2147483648.0:
|
|
return -2147483648
|
|
return int(math.floor(f))
|
|
|
|
def llFrand(lim):
|
|
lim = ff(lim)
|
|
if math.isinf(lim):
|
|
return 0.
|
|
if abs(lim) < float.fromhex('0x1p-126'):
|
|
return -0. if lim < 0 else 0.
|
|
if math.isnan(lim):
|
|
return lim
|
|
|
|
if lslcommon.IsCalc:
|
|
import random
|
|
val = random.random() * lim
|
|
# Truncate, rather than rounding
|
|
m, e = math.frexp(val)
|
|
val = F32(math.ldexp(int(m * 16777216.) * .000000059604644775390625, e))
|
|
if val == lim:
|
|
# this should never happen
|
|
# (it can happen on denormals, but these cause output of 0.0)
|
|
val = 0. # pragma: no cover
|
|
return val
|
|
|
|
# Can't give a concrete value
|
|
raise ELSLCantCompute
|
|
|
|
def llGenerateKey():
|
|
if lslcommon.IsCalc:
|
|
import time
|
|
import random
|
|
|
|
s = hashlib.md5((u'%.17g %f %f' % (time.time(), random.random(),
|
|
random.random())).encode('utf8')
|
|
).hexdigest()
|
|
return Key('-'.join((s[:8], s[8:12], s[12:16], s[16:20], s[20:32])))
|
|
|
|
# Can't give a concrete value
|
|
raise ELSLCantCompute
|
|
|
|
def llGetListEntryType(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
return Types[type(lst[pos])]
|
|
except IndexError:
|
|
# list index out of bounds
|
|
return 0 # TYPE_INVALID
|
|
except KeyError:
|
|
# type of element not in Types
|
|
raise ELSLInvalidType
|
|
|
|
def llGetListLength(lst):
|
|
lst = fl(lst)
|
|
return len(lst)
|
|
|
|
def llGetSubString(s, start, end):
|
|
s = fs(s)
|
|
return InternalGetDeleteSubSequence(s, start, end, isGet=True)
|
|
|
|
def llHash(s):
|
|
s = uniwrap(fs(s))
|
|
hash = 0
|
|
for i in s:
|
|
hash = (hash * 65599 + ord(i)) & 0xFFFFFFFF
|
|
return S32(hash)
|
|
|
|
def llHMAC(pwd, data, alg):
|
|
pwd = bytewrap(uniwrap(fs(pwd)).encode('utf8'))
|
|
data = bytewrap(uniwrap(fs(data)).encode('utf8'))
|
|
alg = fs(alg)
|
|
hash = InternalGetAlg(alg)
|
|
if hash is None:
|
|
raise ELSLCantCompute # spews error
|
|
# Calculate the HMAC here, to avoid requiring yet another module
|
|
if len(pwd) > hash.block_size:
|
|
tmp = hash.copy()
|
|
tmp.update(pwd)
|
|
pwd = bytewrap(tmp.digest())
|
|
del tmp
|
|
if len(pwd) < hash.block_size:
|
|
pwd += bytewrap(b'\x00') * (hash.block_size - len(pwd))
|
|
xorbytes = lambda a, b: bytewrap(x ^ y for (x, y) in zip(a, b))
|
|
ipadded = xorbytes(pwd, bytewrap(b'\x36') * hash.block_size)
|
|
opadded = xorbytes(pwd, bytewrap(b'\x5C') * hash.block_size)
|
|
ohash = hash.copy()
|
|
hash.update(ipadded + data)
|
|
ohash.update(opadded + hash.digest())
|
|
return b64encode(ohash.digest()).decode('utf8')
|
|
|
|
def llInsertString(s, pos, src):
|
|
s = fs(s)
|
|
pos = fi(pos)
|
|
src = fs(src)
|
|
if pos < 0: pos = 0 # llInsertString does not support negative indices
|
|
return s[:pos] + src + s[pos:]
|
|
|
|
def llIntegerToBase64(x):
|
|
x = fi(x)
|
|
return (b64encode(bytewrap(((x>>24)&255, (x>>16)&255, (x>>8)&255, x&255)))
|
|
.decode('utf8'))
|
|
|
|
def llLinear2sRGB(v):
|
|
v = v2f(v)
|
|
return F32(Vector(
|
|
12.920000076293945 * x if x <= 0.0031308000907301903 else
|
|
F32(1.0549999475479126 * F32(x ** 0.4166666567325592))
|
|
- 0.054999999701976776
|
|
for x in v))
|
|
|
|
def llList2CSV(lst):
|
|
lst = fl(lst)
|
|
ret = []
|
|
for elem in lst:
|
|
# This always uses LSO rules for float to string.
|
|
if type(elem) == float:
|
|
if math.isnan(elem) and math.copysign(1.0, elem) < 0:
|
|
ret.append(u'-nan')
|
|
else:
|
|
ret.append(u'%.6f' % elem)
|
|
elif type(elem) in (Vector, Quaternion):
|
|
ret.append(u'<' + llList2CSV(list(elem)) + u'>')
|
|
else:
|
|
ret.append(InternalTypecast(elem, unicode, InList=True, f32=True))
|
|
ret = u', '.join(ret)
|
|
return ret
|
|
|
|
def llList2Float(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
elem = lst[pos]
|
|
if type(elem) == float:
|
|
return elem
|
|
if type(elem) in (int, unicode):
|
|
return InternalTypecast(elem, float, InList=True, f32=True)
|
|
except IndexError:
|
|
pass
|
|
return 0.0
|
|
|
|
def llList2Integer(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
elem = lst[pos]
|
|
if type(elem) == int:
|
|
return elem
|
|
if type(elem) in (float, unicode):
|
|
return InternalTypecast(elem, int, InList=True, f32=True)
|
|
return 0
|
|
except IndexError:
|
|
return 0
|
|
|
|
def llList2Key(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
elem = lst[pos]
|
|
if type(elem) == Key:
|
|
return elem
|
|
if type(elem) == unicode:
|
|
return Key(elem)
|
|
except IndexError:
|
|
pass
|
|
if lslcommon.LSO:
|
|
return Key(NULL_KEY)
|
|
return Key(u'')
|
|
|
|
def llList2List(lst, start, end):
|
|
lst = fl(lst)
|
|
start = fi(start)
|
|
end = fi(end)
|
|
return InternalGetDeleteSubSequence(lst, start, end, isGet=True)
|
|
|
|
def llList2ListSlice(src, start, end, stride, slice_idx):
|
|
src = fl(src)
|
|
start = fi(start)
|
|
end = fi(end)
|
|
stride = fi(stride)
|
|
slice_idx = fi(slice_idx)
|
|
if stride <= 0 or slice_idx < -stride or slice_idx > stride - 1:
|
|
return []
|
|
# Resolve exclusion range exactly like llList2List, generating a new list.
|
|
# NOTE: This bears improvement, by not making an intermediate new list.
|
|
src = InternalGetDeleteSubSequence(src, start, end, isGet=True)
|
|
if slice_idx < 0:
|
|
slice_idx += stride
|
|
return src[slice_idx::stride]
|
|
|
|
def llList2ListStrided(lst, start, end, stride):
|
|
lst = fl(lst)
|
|
start = fi(start)
|
|
end = fi(end)
|
|
stride = fi(stride)
|
|
stride = abs(stride) if stride != 0 else 1
|
|
L = len(lst)
|
|
if start < 0: start += L
|
|
if end < 0: end += L
|
|
if start > end:
|
|
start = 0
|
|
end = L-1
|
|
# start is rounded up to ceil(start/stride)*stride
|
|
start = ((start+stride-1)//stride)*stride
|
|
# end is rounded down to floor(start/stride)*stride
|
|
end = (end//stride)*stride
|
|
|
|
return lst[start:end+1:stride]
|
|
|
|
def llList2Rot(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
elem = lst[pos]
|
|
if type(elem) == Quaternion:
|
|
# The list should not contain integer quaternion components, but
|
|
# we don't err here if not. Instead we return the integer-less
|
|
# quaternion when asked.
|
|
return q2f(elem)
|
|
except IndexError:
|
|
pass
|
|
return ZERO_ROTATION
|
|
|
|
def llList2String(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
return InternalTypecast(lst[pos], unicode, InList=True, f32=True)
|
|
except IndexError:
|
|
pass
|
|
return u''
|
|
|
|
def llList2Vector(lst, pos):
|
|
lst = fl(lst)
|
|
pos = fi(pos)
|
|
try:
|
|
elem = lst[pos]
|
|
if type(elem) == Vector:
|
|
# 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 v2f(elem)
|
|
except IndexError:
|
|
pass
|
|
return ZERO_VECTOR
|
|
|
|
def llListFindList(lst, elems):
|
|
lst = fl(lst)
|
|
elems = fl(elems)
|
|
return InternalListFindList(lst, elems, 0, -1, 1, 0)
|
|
|
|
def llListFindListNext(lst, elems, instance):
|
|
lst = fl(lst)
|
|
elems = fl(elems)
|
|
instance = fi(instance)
|
|
return InternalListFindList(lst, elems, 0, -1, 1, instance)
|
|
|
|
def llListFindStrided(lst, elems, start, end, stride):
|
|
lst = fl(lst)
|
|
elems = fl(elems)
|
|
start = fi(start)
|
|
end = fi(end)
|
|
stride = fi(stride)
|
|
return InternalListFindList(lst, elems, start, end, stride, 0)
|
|
|
|
def llListInsertList(lst, elems, pos):
|
|
lst = fl(lst)
|
|
elems = fl(elems)
|
|
pos = fi(pos)
|
|
# Unlike llInsertString, this function does support negative indices.
|
|
return lst[:pos] + elems + lst[pos:]
|
|
|
|
def llListRandomize(lst, stride):
|
|
if lslcommon.IsCalc:
|
|
lst = fl(lst)
|
|
stride = fi(stride)
|
|
L = len(lst)
|
|
if stride <= 0:
|
|
stride = 1
|
|
if L % stride == 0:
|
|
# valid stride
|
|
L //= stride # length in records
|
|
import random
|
|
for i in range(L - 1):
|
|
# choose which record to swap it with
|
|
rnd = random.randint(i, L - 1)
|
|
# swap each record
|
|
lst[i*stride:(i+1)*stride], lst[rnd*stride:(rnd+1)*stride] = \
|
|
lst[rnd*stride:(rnd+1)*stride], lst[i*stride:(i+1)*stride]
|
|
#else:
|
|
# # if the stride isn't valid, don't modify the list
|
|
# pass
|
|
|
|
return lst
|
|
|
|
# Can't give a concrete value
|
|
raise ELSLCantCompute
|
|
|
|
def llListReplaceList(lst, elems, start, end):
|
|
lst = fl(lst)
|
|
elems = fl(elems)
|
|
start = fi(start)
|
|
end = fi(end)
|
|
L = len(lst)
|
|
if start < -L:
|
|
# llListReplaceList([0,1,2,3],[5],-5,-5) should return [0,1,2,3]
|
|
# llListReplaceList([0,1,2,3],[5],-5,-4) should return [1,2,3]
|
|
# llListReplaceList([0,1,2,3],[5],-5,-7) should return []
|
|
elems = []
|
|
if (start + L if start < 0 else start) > (end + L if end < 0 else end):
|
|
# Exclusion range. Appends elems at 'start' i.e. at end :)
|
|
if end == -1: end += L
|
|
return lst[end+1:start] + elems
|
|
if end == -1: end += L
|
|
return lst[:start] + elems + lst[end+1:]
|
|
|
|
def llListSort(lst, stride, asc, idx=0):
|
|
lst = fl(lst)
|
|
stride = fi(stride)
|
|
asc = fi(asc)
|
|
lst = lst[:] # make a copy
|
|
L = len(lst)
|
|
broken = u'\ufb1a' > u'\U0001d41a' # that happens on Windows
|
|
if stride < 1:
|
|
stride = 1
|
|
if L % stride:
|
|
return lst
|
|
if idx < 0:
|
|
idx += stride
|
|
if not (0 <= idx < stride):
|
|
return []
|
|
for i in xrange(0, L-stride, stride):
|
|
# Optimized by caching the element in the outer loop AND after swapping.
|
|
a = lst[i+idx]
|
|
ta = type(a)
|
|
if ta == Vector:
|
|
a = v2f(a) # list should contain vectors made only of floats
|
|
a = a[0]*a[0] + a[1]*a[1] + a[2]*a[2]
|
|
if lslcommon.LSO:
|
|
# LSO compares bytes, not Unicode.
|
|
a = a.encode('utf8')
|
|
elif broken and ta in (unicode, Key):
|
|
# Note this breaks type consistency between a and ta!
|
|
# It should be OK because only equal types are compared.
|
|
a = a.encode('utf-32-be') # pragma: no cover
|
|
for j in xrange(i+stride, L, stride):
|
|
b = lst[j+idx]
|
|
tb = type(b)
|
|
gt = False
|
|
if ta == tb:
|
|
if tb == Vector:
|
|
b = v2f(b)
|
|
gt = not (a <= b[0]*b[0] + b[1]*b[1] + b[2]*b[2])
|
|
# (note NaNs compare as > thus the reversed condition!)
|
|
elif tb != Quaternion:
|
|
if lslcommon.LSO:
|
|
b = b.encode('utf8')
|
|
elif broken and tb in (unicode, Key):
|
|
b = b.encode('utf-32-be') # pragma: no cover
|
|
gt = not (a <= b) # float, integer, string, key all take this branch
|
|
# (note NaNs compare as > thus the reversed condition!)
|
|
if gt ^ (asc != 1):
|
|
# swap
|
|
lst[i:i+stride],lst[j:j+stride] = lst[j:j+stride],lst[i:i+stride]
|
|
# Re-cache
|
|
a = b
|
|
ta = tb
|
|
if ta == Vector:
|
|
a = v2f(a)
|
|
a = a[0]*a[0] + a[1]*a[1] + a[2]*a[2]
|
|
if broken and ta in (unicode, Key):
|
|
a = a.encode('utf-32-be') # pragma: no cover
|
|
return lst
|
|
|
|
def llListSortStrided(src, stride, idx, ascending):
|
|
idx = fi(idx)
|
|
return llListSort(src, stride, ascending, idx)
|
|
|
|
def llListStatistics(op, lst):
|
|
op = fi(op)
|
|
lst = fl(lst)
|
|
|
|
nums = []
|
|
# Extract numbers in reverse order. LIST_STAT_MEDIAN uses that.
|
|
for elem in lst:
|
|
if type(elem) in (int, float):
|
|
nums.insert(0, float(elem))
|
|
|
|
if nums == []:
|
|
return 0.0
|
|
|
|
if op == 8: # LIST_STAT_NUM_COUNT
|
|
return float(len(nums))
|
|
|
|
if op in (0, 1, 2): # LIST_STAT_RANGE, LIST_STAT_MIN, LIST_STAT_MAX
|
|
min = None
|
|
for elem in nums:
|
|
if min is None:
|
|
min = max = elem
|
|
else:
|
|
if elem < min:
|
|
min = elem
|
|
if elem > max:
|
|
max = elem
|
|
return F32(max - min if op == 0 else min if op == 1 else max)
|
|
|
|
if op == 4: # LIST_STAT_MEDIAN requires special treatment
|
|
# The function behaves very strangely with NaNs. This seems to reproduce it:
|
|
|
|
# llListSort seems to do the right thing with NaNs as needed by the median.
|
|
nums = llListSort(nums, 1, 1)
|
|
L = len(nums)
|
|
if L & 1:
|
|
return F32(nums[L>>1])
|
|
return F32((nums[(L>>1)-1] + nums[L>>1])*0.5)
|
|
|
|
if op in (3, 5, 6, 7): # LIST_STAT_MEAN, STD_DEV, SUM, SUM_SQUARES
|
|
sum = 0.
|
|
sumsq = 0.
|
|
mean = 0.
|
|
N = 0.
|
|
M2 = 0.
|
|
for elem in nums:
|
|
N += 1.
|
|
sum += elem
|
|
sumsq += elem*elem
|
|
delta = elem - mean
|
|
mean += delta/N
|
|
M2 += delta*(elem-mean)
|
|
|
|
if op == 5: # LIST_STAT_STD_DEV
|
|
return 0. if N == 1. else F32(math.sqrt(M2/(N-1.)))
|
|
if op == 6: # LIST_STAT_SUM
|
|
return F32(sum)
|
|
if op == 7: # LIST_STAT_SUM_SQUARES
|
|
return F32(sumsq)
|
|
return F32(mean)
|
|
|
|
if op == 9: # LIST_STAT_GEOMETRIC_MEAN
|
|
N = 0.
|
|
GMlog = 0.
|
|
for elem in nums:
|
|
if elem <= 0.:
|
|
return 0.
|
|
N += 1.
|
|
delta = math.log(elem) - GMlog
|
|
GMlog += delta/N
|
|
return F32(math.exp(GMlog))
|
|
|
|
return 0.0
|
|
|
|
def llLog(f):
|
|
f = ff(f)
|
|
if math.isinf(f) and f < 0 or math.isnan(f) or f <= 0.0:
|
|
return 0.0
|
|
return F32(math.log(f))
|
|
|
|
def llLog10(f):
|
|
f = ff(f)
|
|
if math.isinf(f) and f < 0 or math.isnan(f) or f <= 0.0:
|
|
return 0.0
|
|
return F32(math.log10(f))
|
|
|
|
def llMD5String(s, salt):
|
|
s = fs(s)
|
|
salt = fi(salt)
|
|
return str2u(hashlib.md5(zstr(s).encode('utf8') + b':'
|
|
+ unicode(salt).encode('utf8')).hexdigest(), 'utf8')
|
|
|
|
def llModPow(base, exp, mod):
|
|
base = fi(base)
|
|
exp = fi(exp)
|
|
mod = fi(mod)
|
|
if not lslcommon.IsCalc:
|
|
# This function has a delay, therefore it's not safe to compute it
|
|
# unless in calculator mode.
|
|
raise ELSLCantCompute
|
|
# With some luck, this works fully with native ints on 64 bit machines.
|
|
if mod in (0, 1):
|
|
return 0
|
|
if exp == 0:
|
|
return 1
|
|
# Convert all numbers to unsigned
|
|
base &= 0xFFFFFFFF
|
|
exp &= 0xFFFFFFFF
|
|
mod &= 0xFFFFFFFF
|
|
prod = base
|
|
ret = 1
|
|
while True:
|
|
if exp & 1:
|
|
ret = ((ret * prod) & 0xFFFFFFFF) % mod
|
|
exp = exp >> 1
|
|
if exp == 0:
|
|
break
|
|
prod = ((prod * prod) & 0xFFFFFFFF) % mod
|
|
|
|
return S32(ret)
|
|
|
|
def llOrd(val, index):
|
|
val = uniwrap(fs(val))
|
|
index = fi(index)
|
|
L = len(val)
|
|
if -L <= index < L:
|
|
return ord(val[index]) # we're assuming it won't ever need F32()
|
|
return 0
|
|
|
|
def llParseString2List(s, exc, inc, KeepNulls=False):
|
|
s = fs(s)
|
|
exc = fl(exc)
|
|
inc = fl(inc)
|
|
if s == u'' and KeepNulls:
|
|
return [s]
|
|
exc = exc[:8]
|
|
inc = inc[:8]
|
|
regex = u''
|
|
for i in exc:
|
|
if i != u'':
|
|
regex += u'|' + re.escape(i)
|
|
for i in inc:
|
|
if i != u'':
|
|
regex += u'|' + re.escape(i)
|
|
if regex == u'':
|
|
split = [s]
|
|
else:
|
|
regex = u'(' + regex[1:] + u')'
|
|
split = re.split(regex, s)
|
|
return [i for i in split if (KeepNulls or i != u'') and i not in exc]
|
|
|
|
def llParseStringKeepNulls(s, exc, inc):
|
|
return llParseString2List(s, exc, inc, KeepNulls=True)
|
|
|
|
def llPow(base, exp):
|
|
base = ff(base)
|
|
exp = ff(exp)
|
|
try:
|
|
# Python corner cases and LSL corner cases differ
|
|
|
|
# Python matches these two, but we don't want to get trapped by our own checks.
|
|
if math.isnan(base) or math.isnan(exp):
|
|
return NaN
|
|
if exp == 0.0:
|
|
return 1.0
|
|
|
|
if base == 0.0: # Python gives exception on these, LSL returns stuff
|
|
if math.isinf(exp) and exp < 0:
|
|
return Infinity # llPow(0.0, -inf) = inf
|
|
|
|
if exp < 0.0:
|
|
# Negative finite exponent cases
|
|
if math.copysign(1, base) < 0 and exp.is_integer() and not (exp/2.).is_integer():
|
|
return -Infinity # llPow(-0.0, -odd_integer) = -inf
|
|
return Infinity
|
|
|
|
elif abs(base) == 1.0 and math.isinf(exp):
|
|
return NaN # Python says 1.0
|
|
|
|
f = F32(math.pow(base, exp))
|
|
return 0.0 if f == 0.0 else f # don't return -0.0
|
|
except ValueError: # should happen only with negative base and noninteger exponent
|
|
return Indet
|
|
|
|
def llReplaceSubString(source, search, replace, count):
|
|
source = fs(source)
|
|
search = fs(search)
|
|
replace = fs(replace)
|
|
count = fi(count)
|
|
|
|
if not search:
|
|
return source
|
|
if count == 0:
|
|
return source.replace(search, replace)
|
|
if count > 0:
|
|
return source.replace(search, replace, count)
|
|
# Replace *last* occurrences of "search" in "source"
|
|
slen = len(search)
|
|
i = len(source)
|
|
while True:
|
|
i = source.rfind(search, 0, i)
|
|
if i < 0:
|
|
break
|
|
source = source[:i] + replace + source[i + slen:]
|
|
count += 1
|
|
if count == 0:
|
|
break
|
|
return source
|
|
|
|
def llRot2Angle(r):
|
|
r = q2f(r)
|
|
# Used by llAngleBetween.
|
|
# Version based on research by Moon Metty, Miranda Umino and Strife Onizuka
|
|
return F32(2.*math.atan2(math.sqrt(math.fsum((r[0]*r[0], r[1]*r[1], r[2]*r[2]))), abs(r[3])));
|
|
|
|
def llRot2Axis(r):
|
|
r = q2f(r)
|
|
if r[3] < 0:
|
|
return llVecNorm(Vector((-r[0], -r[1], -r[2])))
|
|
return llVecNorm(Vector((r[0], r[1], r[2])))
|
|
|
|
def llRot2Euler(r):
|
|
r = q2f(r)
|
|
|
|
# Another one of the hardest. The formula for Z angle in the
|
|
# singularity case was inspired by the viewer code.
|
|
r = qnorm(r)
|
|
y = 2*(r[0]*r[2] + r[1]*r[3])
|
|
|
|
# Check gimbal lock condition
|
|
if abs(y) > 0.99999:
|
|
return Vector(F32((0.,
|
|
math.asin(1. if y > 1. else y),
|
|
math.atan2(r[2]*r[3]+r[0]*r[1],
|
|
.5-(r[0]*r[0]+r[2]*r[2]))
|
|
)))
|
|
|
|
qy2 = r[1]*r[1]
|
|
return Vector(F32((
|
|
math.atan2(r[0]*r[3]-r[1]*r[2], .5-(r[0]*r[0]+qy2)),
|
|
math.asin(y),
|
|
math.atan2(r[2]*r[3]-r[0]*r[1], .5-(r[2]*r[2]+qy2))
|
|
)))
|
|
|
|
def llRot2Fwd(r):
|
|
r = q2f(r)
|
|
v = Vector((1., 0., 0.))
|
|
return llVecNorm(mul(v, qnz(r), f32=False))
|
|
|
|
def llRot2Left(r):
|
|
r = q2f(r)
|
|
v = Vector((0., 1., 0.))
|
|
return llVecNorm(mul(v, qnz(r), f32=False))
|
|
|
|
def llRot2Up(r):
|
|
r = q2f(r)
|
|
v = Vector((0., 0., 1.))
|
|
return llVecNorm(mul(v, qnz(r), f32=False))
|
|
|
|
def llRotBetween(v1, v2):
|
|
v1 = v2f(v1)
|
|
v2 = v2f(v2)
|
|
|
|
# Loosely based on the "Bad" reference implementation and
|
|
# on SL source code (pre Moon Metty's changes).
|
|
# See <https://bitbucket.org/lindenlab/viewer-release/src/015080d8/indra/llmath/llquaternion.cpp#llquaternion.cpp-425>
|
|
v1 = llVecNorm(v1)
|
|
v2 = llVecNorm(v2)
|
|
dot = mul(v1, v2)
|
|
axis = mod(v1, v2)
|
|
threshold = float.fromhex('0x1.fffffcp-1')
|
|
if -threshold <= dot <= threshold:
|
|
# non-aligned - their cross product is a good axis
|
|
m = math.sqrt(mul(axis, axis) + (dot + 1.) * (dot + 1.))
|
|
return Quaternion(F32((axis[0] / m, axis[1] / m, axis[2] / m,
|
|
(dot + 1.) / m)))
|
|
# about aligned - two cases to deal with
|
|
if dot > 0.:
|
|
# same signs
|
|
return Quaternion((0., 0., 0., 1.))
|
|
# opposite signs - find one vector in the plane perpendicular to
|
|
# either vector, to use as axis. We do this by choosing an arbitrary
|
|
# vector (<1,0,0> in our case), and calculating the cross product with it,
|
|
# which will be perpendicular to both. But matching the SL results requires
|
|
# another cross product of the input with the result, so we do that.
|
|
ortho = mod(mod(v1, Vector((1., 0., 0.))), v1)
|
|
ortho = Vector((0. if f == 0. else f for f in ortho)) # remove minus zero
|
|
m = mul(ortho, ortho)
|
|
if m < float.fromhex('0x1.b7cdfep-34'):
|
|
# The input vectors were aligned with <1,0,0>, so this was not a
|
|
# good choice. Return 180 deg. rotation over Z instead.
|
|
return Quaternion((0., 0., 1., 0.))
|
|
m = math.sqrt(m)
|
|
return Quaternion(F32((ortho[0] / m, ortho[1] / m, ortho[2] / m, 0.)))
|
|
|
|
def llRound(f):
|
|
f = ff(f)
|
|
if math.isnan(f) or math.isinf(f) or f >= 2147483647.5 or f < -2147483648.0:
|
|
return -2147483648
|
|
return int(math.floor(F32(f+0.5)))
|
|
|
|
def llSHA1String(s):
|
|
s = fs(s)
|
|
return str2u(hashlib.sha1(s.encode('utf8')).hexdigest(), 'utf8')
|
|
|
|
def llSHA256String(s):
|
|
s = fs(s)
|
|
return str2u(hashlib.sha256(s.encode('utf8')).hexdigest(), 'utf8')
|
|
|
|
def llSin(f):
|
|
f = ff(f)
|
|
if math.isinf(f):
|
|
return Indet
|
|
if -9223372036854775808.0 < f < 9223372036854775808.0:
|
|
return F32(math.sin(reduce(f)))
|
|
return f
|
|
|
|
def llSqrt(f):
|
|
f = ff(f)
|
|
if f < 0.0:
|
|
return Indet
|
|
# LSL and Python both produce -0.0 when the input is -0.0.
|
|
return F32(math.sqrt(f))
|
|
|
|
def llsRGB2Linear(v):
|
|
v = v2f(v)
|
|
return F32(Vector(
|
|
x / 12.920000076293945 if x <= 0.040449999272823334 else
|
|
F32(F32(x + 0.054999999701976776) / 1.0549999475479126)
|
|
** 2.4000000953674316
|
|
for x in v))
|
|
|
|
def llStringLength(s):
|
|
s = fs(s)
|
|
return len(s)
|
|
|
|
def llStringToBase64(s):
|
|
s = fs(s)
|
|
return b64encode(s.encode('utf8')).decode('utf8')
|
|
|
|
def llStringTrim(s, mode):
|
|
s = fs(s)
|
|
mode = fi(mode)
|
|
head = 0
|
|
length = len(s)
|
|
tail = length-1
|
|
if mode & 1: # STRING_TRIM_HEAD
|
|
while head < length and s[head] in u'\x09\x0a\x0b\x0c\x0d\x20':
|
|
head += 1
|
|
if mode & 2: # STRING_TRIM_TAIL
|
|
while tail >= head and s[tail] in u'\x09\x0a\x0b\x0c\x0d\x20':
|
|
tail -= 1
|
|
return s[head:tail+1]
|
|
|
|
def llSubStringIndex(s, pattern):
|
|
s = fs(s)
|
|
pattern = fs(pattern)
|
|
return s.find(pattern)
|
|
|
|
def llTan(f):
|
|
f = ff(f)
|
|
if math.isinf(f):
|
|
return Indet
|
|
if -9223372036854775808.0 < f < 9223372036854775808.0:
|
|
return F32(math.tan(reduce(f)))
|
|
return f
|
|
|
|
def llToLower(s):
|
|
s = fs(s)
|
|
if lslcommon.LSO:
|
|
return zstr(s).translate(lowerLSOMap)
|
|
return zstr(s).translate(lowerMap)
|
|
|
|
def llToUpper(s):
|
|
s = fs(s)
|
|
if lslcommon.LSO:
|
|
return zstr(s).translate(upperLSOMap)
|
|
return zstr(s).translate(upperMap)
|
|
|
|
def llUnescapeURL(s):
|
|
s = fs(s)
|
|
ret = bytearray(b'')
|
|
L = len(s)
|
|
i = 0
|
|
while i < L:
|
|
c = s[i]
|
|
i += 1
|
|
if c != u'%':
|
|
ret += c.encode('utf8')
|
|
continue
|
|
if i >= L:
|
|
break
|
|
c = s[i] # First digit
|
|
i += 1
|
|
if i >= L:
|
|
break
|
|
v = 0
|
|
if u'0' <= c <= u'9' or u'A' <= c <= u'F' or u'a' <= c <= u'f':
|
|
v = int(c, 16)<<4
|
|
c = s[i] # Second digit
|
|
if c == u'%':
|
|
ret.append(v)
|
|
i += 1
|
|
continue
|
|
i += 1
|
|
if u'0' <= c <= u'9' or u'A' <= c <= u'F' or u'a' <= c <= u'f':
|
|
v += int(c, 16)
|
|
ret.append(v)
|
|
return InternalUTF8toString(ret)
|
|
|
|
def llVecDist(v1, v2):
|
|
v1 = v2f(v1)
|
|
v2 = v2f(v2)
|
|
# 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):
|
|
v = v2f(v)
|
|
return F32(math.sqrt(math.fsum((v[0]*v[0], v[1]*v[1], v[2]*v[2]))))
|
|
|
|
def llVecNorm(v, f32 = True):
|
|
v = v2f(v)
|
|
if v == ZERO_VECTOR:
|
|
return v
|
|
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)
|
|
|
|
def llXorBase64(s, xor):
|
|
s = fs(s)
|
|
xor = fs(xor)
|
|
|
|
# Xor the underlying bytes.
|
|
|
|
if xor == u'':
|
|
return s
|
|
|
|
s = b64_re.search(s).group(0)
|
|
L1 = len(s)
|
|
xor = b64_re.search(xor).group(0)
|
|
L2 = len(xor)
|
|
|
|
if L2 == 0:
|
|
# The input xor string starts with zero or one valid Base64 characters.
|
|
L2 = 2
|
|
xor = u'AA'
|
|
|
|
s = bytearray(b64decode(s + u'=' * (-L1 & 3)))
|
|
xor = bytearray(b64decode(xor + u'=' * (-L2 & 3)))
|
|
L2 = len(xor)
|
|
|
|
i = 0
|
|
ret = bytearray(b'')
|
|
|
|
Bug3763 = 3763 in Bugs
|
|
# BUG-3763 consists of the binary string having an extra NULL every time
|
|
# after the second repetition of the XOR pattern. For example, if the XOR
|
|
# binary string is b'pqr' and the input string is b'12345678901234567890',
|
|
# the XOR binary string behaves as if it was b'pqrpqr\0pqr\0pqr\0pqr\0pq'.
|
|
# We emulate that by adding the zero and increasing the length the first
|
|
# time.
|
|
for c in s:
|
|
ret.append(c ^ xor[i])
|
|
i += 1
|
|
if i >= L2:
|
|
i = 0
|
|
if Bug3763:
|
|
Bug3763 = False
|
|
xor.append(0)
|
|
L2 += 1
|
|
return b64encode(ret).decode('utf8')
|
|
|
|
def llXorBase64Strings(s, xor):
|
|
s = fs(s)
|
|
xor = fs(xor)
|
|
|
|
if not lslcommon.IsCalc:
|
|
# This function has a delay, therefore it's not safe to compute it
|
|
# unless in calculator mode.
|
|
raise ELSLCantCompute
|
|
|
|
if xor == u'':
|
|
return s
|
|
|
|
B64 = u'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
|
|
# Special case when the first character is not a Base64 one. (LL's ways are inextricable)
|
|
base = B64.find(xor[0])
|
|
if base < 0:
|
|
if xor[0] == u'=':
|
|
xor = u'+' + xor[1:]
|
|
base = 62
|
|
else:
|
|
xor = u'/' + xor[1:]
|
|
base = 63
|
|
|
|
ret = u''
|
|
i = 0
|
|
L = len(xor)
|
|
for c1 in s:
|
|
val1 = B64.find(c1)
|
|
val2 = B64.find(xor[i])
|
|
i += 1
|
|
if i >= L:
|
|
i = 0
|
|
|
|
if val1 < 0:
|
|
ret += u'='
|
|
else:
|
|
if val2 < 0:
|
|
val2 = base
|
|
i = 1
|
|
ret += B64[val1 ^ val2]
|
|
return ret
|
|
|
|
def llXorBase64StringsCorrect(s, xor):
|
|
s = fs(s)
|
|
xor = fs(xor)
|
|
|
|
# Xor the underlying bytes but repeating the xor parameter pattern at the first zero (SCR-35).
|
|
|
|
if xor == u'':
|
|
return s
|
|
|
|
|
|
s = b64_re.search(s).group(0)
|
|
L1 = len(s)
|
|
xor = b64_re.search(xor).group(0)
|
|
L2 = len(xor)
|
|
|
|
if L2 == 0:
|
|
# The input xor string starts with zero or one valid Base64 characters.
|
|
L2 = 2
|
|
xor = u'AA'
|
|
|
|
s = bytearray(b64decode(s + u'=' * (-L1 & 3)))
|
|
xor = bytearray(b64decode(xor + u'=' * (-L2 & 3)) + b'\x00')
|
|
|
|
i = 0
|
|
ret = bytearray(b'')
|
|
|
|
for c in s:
|
|
ret.append(c ^ xor[i])
|
|
i += 1
|
|
if xor[i] == 0:
|
|
i = 0
|
|
return b64encode(ret).decode('utf8')
|
|
|
|
# Create upper and lower tables
|
|
def __make_tables():
|
|
global lowerMap, lowerLSOMap
|
|
global upperMap, upperLSOMap
|
|
|
|
lowerLSOMap = bytearray(range(256))
|
|
for i in range(ord('A'), ord('Z') + 1):
|
|
lowerLSOMap[i] = i + 32
|
|
|
|
upperLSOMap = bytearray(range(256))
|
|
for i in range(ord('a'), ord('z') + 1):
|
|
upperLSOMap[i] = i - 32
|
|
|
|
lowerMap = {}
|
|
upperMap = {}
|
|
|
|
def lohi(a, b):
|
|
lowerMap[a] = b
|
|
upperMap[b] = a
|
|
|
|
for i in range(65, 90+1):
|
|
lohi(i, i+32)
|
|
|
|
for i in range(192, 222+1):
|
|
if i != 215:
|
|
lohi(i, i+32)
|
|
|
|
|
|
for i in range(256, 311+1, 2):
|
|
if i != 304:
|
|
lohi(i, i+1)
|
|
|
|
for i in range(313, 328+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
for i in range(330, 375+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
lohi(376, 255)
|
|
|
|
for i in range(377, 382+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
lohi(385, 595)
|
|
|
|
for i in range(386, 389+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
lohi(390, 596)
|
|
lohi(391, 392)
|
|
|
|
lohi(393, 598)
|
|
lohi(394, 599)
|
|
|
|
lohi(395, 396)
|
|
|
|
lohi(398, 477)
|
|
lohi(399, 601)
|
|
lohi(400, 603)
|
|
|
|
lohi(401, 402)
|
|
|
|
lohi(403, 608)
|
|
lohi(404, 611)
|
|
lohi(406, 617)
|
|
lohi(407, 616)
|
|
|
|
lohi(408, 409)
|
|
|
|
lohi(412, 623)
|
|
lohi(413, 626)
|
|
lohi(415, 629)
|
|
|
|
lohi(416, 417)
|
|
lohi(418, 419)
|
|
lohi(420, 421)
|
|
|
|
lohi(423, 424)
|
|
|
|
lohi(425, 643)
|
|
|
|
lohi(428, 429)
|
|
|
|
lohi(430, 648)
|
|
|
|
lohi(431, 432)
|
|
|
|
lohi(433, 650)
|
|
lohi(434, 651)
|
|
|
|
lohi(435, 436)
|
|
lohi(437, 438)
|
|
|
|
lohi(439, 658)
|
|
|
|
lohi(440, 441)
|
|
lohi(444, 445)
|
|
|
|
lohi(452, 454)
|
|
lohi(455, 457)
|
|
lohi(458, 460)
|
|
|
|
for i in range(461, 476 + 1, 2):
|
|
lohi(i, i+1)
|
|
|
|
for i in range(478, 495 + 1, 2):
|
|
lohi(i, i+1)
|
|
|
|
lohi(497, 499)
|
|
|
|
lohi(500, 501)
|
|
|
|
for i in range(506, 535 + 1, 2):
|
|
lohi(i, i+1)
|
|
|
|
lohi(902, 940)
|
|
lohi(904, 941)
|
|
lohi(905, 942)
|
|
lohi(906, 943)
|
|
lohi(908, 972)
|
|
lohi(910, 973)
|
|
lohi(911, 974)
|
|
|
|
for i in range(913, 939+1):
|
|
if i != 930:
|
|
lohi(i, i+32)
|
|
|
|
# Sigma at end of word -> Upper case Sigma but no reverse mapping
|
|
upperMap[962] = 931
|
|
|
|
for i in range(994, 1007+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
for i in range(1025, 1039+1):
|
|
if i != 1037:
|
|
lohi(i, i+80)
|
|
|
|
for i in range(1040, 1071+1):
|
|
lohi(i, i+32)
|
|
|
|
for i in range(1120, 1153+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
for i in range(1168, 1215+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
lohi(1217, 1218)
|
|
lohi(1219, 1220)
|
|
lohi(1223, 1224)
|
|
lohi(1227, 1228)
|
|
|
|
for i in range(1232, 1269+1, 2):
|
|
if i != 1260:
|
|
lohi(i, i+1)
|
|
|
|
lohi(1272, 1273)
|
|
|
|
for i in range(1329, 1366+1):
|
|
lohi(i, i+48)
|
|
|
|
# Asymmetrical, these can't be upper()'d back
|
|
for i in range(4256, 4293+1):
|
|
lowerMap[i] = i + 48
|
|
|
|
for i in range(7680, 7829+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
for i in range(7840, 7929+1, 2):
|
|
lohi(i, i+1)
|
|
|
|
for i in range(7944, 7951+1):
|
|
lohi(i, i-8)
|
|
|
|
for i in range(7960, 7965+1):
|
|
lohi(i, i-8)
|
|
|
|
for i in range(7976, 7983+1):
|
|
lohi(i, i-8)
|
|
|
|
for i in range(7992, 7999+1):
|
|
lohi(i, i-8)
|
|
|
|
for i in range(8008, 8013+1):
|
|
lohi(i, i-8)
|
|
|
|
lohi(8025, 8017)
|
|
lohi(8027, 8019)
|
|
lohi(8029, 8021)
|
|
lohi(8031, 8023)
|
|
|
|
for i in range(8040, 8047+1):
|
|
lohi(i, i-8)
|
|
|
|
lohi(8120, 8112)
|
|
lohi(8121, 8113)
|
|
|
|
lohi(8122, 8048)
|
|
lohi(8123, 8049)
|
|
|
|
for i in range(8136, 8139+1):
|
|
lohi(i, i-86)
|
|
|
|
lohi(8152, 8144)
|
|
lohi(8153, 8145)
|
|
lohi(8154, 8054)
|
|
lohi(8155, 8055)
|
|
lohi(8168, 8160)
|
|
lohi(8169, 8161)
|
|
lohi(8170, 8058)
|
|
lohi(8171, 8059)
|
|
lohi(8172, 8165)
|
|
lohi(8184, 8056)
|
|
lohi(8185, 8057)
|
|
lohi(8186, 8060)
|
|
lohi(8187, 8061)
|
|
|
|
for i in range(8544, 8559+1):
|
|
lohi(i, i+16)
|
|
|
|
for i in range(9398, 9423+1):
|
|
lohi(i, i+26)
|
|
|
|
for i in range(65313, 65338+1):
|
|
lohi(i, i+32)
|
|
|
|
__make_tables()
|
|
|
|
|
|
lslbasefuncs_used = True
|