def LoadLibrary(builtins=None, fndata=None): """Load builtins.txt and fndata.txt (or the given filenames) and return a tuple with the events, constants and functions, each in a dict. """ if builtins is None: builtins = lslcommon.DataPath + 'builtins.txt' if fndata is None: fndata = lslcommon.DataPath + 'fndata.txt' events = {} constants = {} functions = {} # Library read code parse_lin_re = re.compile(br'^\s*([a-z]+)\s+' br'([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*(' br'[a-z]+\s+[a-zA-Z_][a-zA-Z0-9_]*' br'(?:\s*,\s*[a-z]+\s+[a-zA-Z_][a-zA-Z0-9_]*)*' br')?\s*\)\s*$' br'|' br'^\s*const\s+([a-z]+)' br'\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*?)\s*$' br'|' br'^\s*(?:#.*|//.*)?$') parse_arg_re = re.compile(br'^\s*([a-z]+)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*$') parse_fp_re = re.compile(br'^\s*(-?(?=[0-9]|\.[0-9])[0-9]*' br'((?:\.[0-9]*)?(?:[Ee][+-]?[0-9]+)?))\s*$') parse_int_re = re.compile(br'^\s*(-?0x[0-9A-Fa-f]+|-?[0-9]+)\s*$') parse_str_re = re.compile(u'^"((?:[^"\\\\]|\\\\.)*)"$') f = open(builtins, 'rb') try: linenum = 0 try: ubuiltins = builtins.decode(sys.getfilesystemencoding()) except UnicodeDecodeError: # This is just a guess at the filename encoding. ubuiltins = builtins.decode('iso-8859-15') while True: linenum += 1 line = f.readline() if not line: break if line[-1] == '\n': line = line[:-1] try: uline = line.decode('utf8') except UnicodeDecodeError: warning(u"Bad Unicode in %s line %d" % (ubuiltins, linenum)) continue match = parse_lin_re.search(line) if not match: warning(u"Syntax error in %s, line %d" % (ubuiltins, linenum)) continue if match.group(1): # event or function typ = match.group(1) if typ == 'quaternion': typ = 'rotation' if typ == 'void': typ = None elif typ != 'event' and typ not in types: warning(u"Invalid type in %s, line %d: %s" % (ubuiltins, linenum, typ)) continue args = [] arglist = match.group(3) if arglist: arglist = arglist.split(',') bad = False for arg in arglist: argtyp = parse_arg_re.search(arg).group(1) if argtyp not in types: uargtyp = argtyp.decode('utf8') warning(u"Invalid type in %s, line %d: %s" % (ubuiltins, linenum, uargtyp)) del uargtyp bad = True break args.append(argtyp) if bad: continue name = match.group(2) if typ == 'event': if name in events: uname = name.decode('utf8') warning(u"Event at line %d was already defined in %s," u" overwriting: %s" % (linenum, ubuiltins, uname)) del uname events[name] = {'pt': tuple(args), 'NeedsData': True} else: # Library functions go to the functions table. If # they are implemented in lslfuncs.*, they get a # reference to the implementation; otherwise None. if name in functions: uname = name.decode('utf8') warning(u"Function at line %d was already defined" u" in %s, overwriting: %s" % (linenum, ubuiltins, uname)) del uname fn = getattr(lslfuncs, name, None) functions[name] = { 'Kind': 'f', 'Type': typ, 'uns': True, 'ParamTypes': args, 'NeedsData': True } if fn is not None: functions[name]['Fn'] = fn elif match.group(4): # constant name = match.group(5) if name in constants: uname = name.decode('utf8') warning(u"Global at line %d was already defined in %s," u" overwriting: %s" % (linenum, ubuiltins, uname)) del uname typ = match.group(4) if typ not in types: utyp = typ.decode('utf8') warning(u"Invalid type in %s, line %d: %s" % (ubuiltins, linenum, utyp)) del utyp continue if typ == 'quaternion': typ = 'rotation' value = match.group(6) if typ == 'integer': value = int(value, 0) elif typ == 'float': value = lslfuncs.F32(float(value)) elif typ == 'string': value = value.decode('utf8') if parse_str_re.search(value): esc = False tmp = value[1:-1] value = u'' for c in tmp: if esc: if c == u'n': c = u'\n' elif c == u't': c = u' ' value += c esc = False elif c == u'\\': esc = True else: value += c #if typ == 'key': # value = Key(value) else: warning(u"Invalid string in %s line %d: %s" % (ubuiltins, linenum, uline)) value = None elif typ == 'key': warning(u"Key constants not supported in %s, line %d: %s" % (ubuiltins, linenum, uline)) value = None elif typ in ('vector', 'rotation'): try: if value[0:1] != '<' or value[-1:] != '>': raise ValueError value = value[1:-1].split(',') if len(value) != (3 if typ == 'vector' else 4): raise ValueError num = parse_fp_re.search(value[0]) if not num: raise ValueError value[0] = lslfuncs.F32(float(num.group(1))) num = parse_fp_re.search(value[1]) if not num: raise ValueError value[1] = lslfuncs.F32(float(num.group(1))) num = parse_fp_re.search(value[2]) if not num: raise ValueError value[2] = lslfuncs.F32(float(num.group(1))) if typ == 'vector': value = Vector(value) else: num = parse_fp_re.search(value[3]) if not num: raise ValueError value[3] = lslfuncs.F32(float(num.group(1))) value = Quaternion(value) except ValueError: warning(u"Invalid vector/rotation syntax in %s" u" line %d: %s" % (ubuiltins, linenum, uline)) else: assert typ == 'list' if value[0:1] != '[' or value[-1:] != ']': warning(u"Invalid list value in %s, line %d: %s" % (ubuiltins, linenum, uline)) elif value[1:-1].strip() != '': warning(u"Non-empty list constants not supported" u" in %s, line %d: %s" % (ubuiltins, linenum, uline)) value = None else: value = [] if value is not None: constants[name] = value finally: f.close() # Load the function data table as well. parse_flag_re = re.compile( r'^\s*-\s+(?:(?:(sef)|return\s+' r'("(?:\\.|[^"])*"|<[^>]+>|[-+0-9x.e]+' # strings, vectors, numbers r'|\[(?:[^]"]|"(?:\\.|[^"])*")*\]))' # lists r'(?:\s+if\s+(.*\S))?' r'|(unstable|stop|strlen|detect|touch|grab)' r'|(min|max|delay)\s+([-0-9.]+)' r'|listto\s+(integer|float|string|key|vector|rotation|list)' r')\s*$', re.I) # TODO: "quaternion" doesn't compare equal to "rotation" even if they are # equivalent. Canonicalize it before comparison, to avoid false # reports of mismatches. f = open(fndata, 'rb') try: linenum = 0 curr_fn = None curr_ty = None skipping = False try: ufndata = fndata.decode(sys.getfilesystemencoding()) except UnicodeDecodeError: # This is just a guess at the filename encoding. ufndata = fndata.decode('iso-8859-15') while True: linenum += 1 line = f.readline() if not line: break if line[-1] == '\n': line = line[:-1] try: uline = line.decode('utf8') except UnicodeDecodeError: warning(u"Bad Unicode in %s line %d" % (ufndata, linenum)) continue match_fn = parse_lin_re.search(line) if match_fn and not match_fn.group(4) and not match_fn.group(1): # comment or empty continue rettype = match_fn.group(1) if match_fn else None if match_fn and (rettype in ('void', 'event') or rettype in types): skipping = True # until proven otherwise name = match_fn.group(2) uname = name.decode('utf8') if (rettype == 'event' and name not in events or rettype != 'event' and name not in functions): warning(u"%s %s is not in builtins, in %s line %d," u" skipping." % (u"Function" if rettype != 'event' else u"Event", uname, ufndata, linenum)) continue rettype = rettype if rettype != 'void' else None if 'event' != rettype != functions[name]['Type']: warning(u"Function %s returns invalid type, in %s line %d," u" skipping." % (uname, ufndata, linenum)) continue argnames = [] arglist = match_fn.group(3) current_args = (functions[name]['ParamTypes'] if rettype != 'event' else events[name]['pt']) if arglist: arglist = arglist.split(',') if len(current_args) != len(arglist): warning(u"Parameter list mismatch in %s line %d," u" %s %s. Skipping." % (ufndata, linenum, u"function" if rettype != 'event' else u"event", uname)) continue bad = False # used to 'continue' at this loop level for idx, arg in enumerate(arglist): argmatch = parse_arg_re.search(arg) argtyp = argmatch.group(1) argname = argmatch.group(2) if current_args[idx] != argtyp: warning(u"Parameter list mismatch in %s line %d," u" %s %s. Skipping." % (ufndata, linenum, u"function" if rettype != 'event' else u"event", uname)) bad = True break argnames.append(argname) if bad: del bad continue del bad if 'NeedsData' not in (functions[name] if rettype != 'event' else events[name]): warning(u"Duplicate %s %s in %s line %d. Skipping." % (u"function" if rettype != 'event' else u"event", uname, ufndata, linenum)) continue # passed all tests curr_fn = name curr_ty = rettype skipping = False if curr_ty == 'event': del events[name]['NeedsData'] else: del functions[name]['NeedsData'], functions[name]['uns'] else: match_flag = parse_flag_re.search(line) if match_flag: if curr_fn is None and not skipping: warning(u"Flags present before any function or event" u" in %s line %d: %s" % (ufndata, linenum, uline)) skipping = True continue if not skipping: ucurr_fn = curr_fn.decode('utf8') if match_flag.group(1): # SEF # We don't handle conditions yet. Take the # condition as never met for now (every function # that is conditionally SEF is taken as not SEF) if curr_ty == 'event' and match_flag.group(3): warning(u"Events do not support conditions" u" in SEF flags, in line %d, event %s." u" Omitting: %s" % (linenum, ucurr_fn, uline)) continue elif curr_ty == 'event': events[curr_fn]['SEF'] = True else: if not match_flag.group(3): functions[curr_fn]['SEF'] = True elif curr_ty == 'event': if match_flag.group(4): flag = match_flag.group(4).lower() if flag in ('detect', 'touch', 'grab'): events[curr_fn][flag] = True else: warning(u"Events only support a few flags" u", in line %d, event %s." u" Omitting: %s" % (linenum, ucurr_fn, uline)) continue elif match_flag.group(2): pass # return not handled yet elif match_flag.group(4): flag = match_flag.group(4).lower() if flag == 'unstable': functions[curr_fn]['uns'] = True else: functions[curr_fn][flag] = True elif match_flag.group(5): if match_flag.group(5).lower() in ('min', 'max'): minmax = match_flag.group(5).lower() value = match_flag.group(6) typ = functions[curr_fn]['Type'] if typ == 'integer': good = parse_int_re.search(value) if good: value = lslfuncs.S32( int(good.group(1), 0)) elif typ == 'float': good = parse_fp_re.search(value) if good: value = lslfuncs.F32( float(good.group(1))) else: good = False if good: functions[curr_fn][minmax] = value else: warning( u"Type mismatch or value error in %s" u" line %d: %s" % (ufndata, linenum, uline)) continue else: # delay value = parse_fp_re.search(match_flag.group(6)) if not value: warning(u"Invalid delay value in %s" u" line %d: %s" % (ufndata, linenum, uline)) continue value = float(value.group(1)) # no need to F32 if value != 0 and 'SEF' in functions[curr_fn]: warning(u"Side-effect-free function" u" %s contradicts delay, in %s" u" line %d" % (ucurr_fn, ufndata, linenum)) continue functions[curr_fn]['delay'] = value elif match_flag.group(7): functions[curr_fn]['ListTo'] = match_flag.group(7) continue else: pass else: warning(u"Syntax error in %s line %d, skipping: %s" % (ufndata, linenum, uline)) continue finally: f.close() # Post-checks for i in functions: ui = i.decode('utf8') if 'NeedsData' in functions[i]: del functions[i]['NeedsData'] warning(u"Library data, file %s: Function %s has no data." % (ufndata, ui)) if 'min' in functions[i] and 'max' in functions[i]: if functions[i]['min'] > functions[i]['max']: warning(u"Library data: Function %s has min > max:" u" min=%s max=%s, removing both." % (ui, repr(functions[i]['min']).decode('utf8'), repr(functions[i]['max']))) del functions[i]['min'], functions[i]['max'] if 'SEF' in functions[i] and 'delay' in functions[i]: warning(u"Library data: Side-effect-free function %s contradicts" u" delay. Removing SEF." % ui) del functions[i]['SEF'] for i in events: ui = i.decode('utf8') if 'NeedsData' in events[i]: del events[i]['NeedsData'] warning(u"Library data, file %s: Event %s has no data." % (ufndata, ui)) return events, constants, functions
def Value2LSL(self, value): tvalue = type(value) if tvalue in (Key, unicode): pfx = sfx = '' if type(value) == Key: # Constants of type key can not be represented #raise lslfuncs.ELSLTypeMismatch # Actually they can be the result of folding. # On second thought, if we report the error, the location info # is lost. So we emit a warning instead, letting the compiler # report the error in the generated source. if self.globalmode and self.listmode: warning(u"Illegal combo: Key type inside a global list") if self.listmode or not self.globalmode: if self.globalmode: pfx = '(key)' else: pfx = '((key)' sfx = ')' if u'\t' in value and self.warntabs: warning(u"A string contains a tab. Tabs are expanded to four" " spaces by the viewer when copy-pasting the code" " (disable this warning by disabling the 'warntabs'" " option).") return pfx + '"' + value.encode('utf8').replace('\\','\\\\') \ .replace('"','\\"').replace('\n','\\n') + '"' + sfx if tvalue == int: if value < 0 and not self.globalmode and self.optsigns: #return '0x%X' % (value + 4294967296) return '((integer)' + str(value) + ')' return str(value) if tvalue == float: if self.optfloats and value.is_integer( ) and -2147483648.0 <= value < 2147483648.0: if self.globalmode and not self.listmode: if value == 0 and copysign(1, value) == -1: return '-0.' return str(int(value)) elif not self.globalmode: # Important inside lists!! if value == 0 and copysign(1, value) == -1: return '(-(float)0)' return '((float)' + str(int(value)) + ')' s = repr(value) if s == 'nan': return '(1e40*0)' if copysign(1, value) < 0 else '(-1e40*0)' if s == 'inf': return '1e40' if s == '-inf': return '-1e40' if self.globalmode else '((float)-1e40)' # Try to remove as many decimals as possible but keeping the F32 value intact exp = s.find('e') if ~exp: s, exp = s[:exp], s[exp:] if exp[1] == '+': exp = exp[:1] + exp[2:] if '.' not in s: # I couldn't produce one but it's assumed that if it happens, # this code deals with it correctly s += '.' # pragma: no cover else: if '.' not in s: # This should never happen (Python should always return a point or exponent) return s + '.' # pragma: no cover exp = '' # Shorten the float as much as possible. while s[-1] != '.' and lslfuncs.F32(float(s[:-1] + exp)) == value: s = s[:-1] if s[-1] != '.': news = s[:-1] neg = '' if s[0] == '-': news = news[1:] neg = '-' # Try harder point = news.index('.') + 1 - len(news) if point: news = str(int(news[:point - 1] + news[point:]) + 1).zfill( len(news) - 1) # Increment else: news = str(int(news[:-1]) + 1).zfill(len(news) - 1) news = news[:point + len(news)] + '.' + news[ point + len(news):] # Reinsert point # Repeat the operation with the incremented number while news[-1] != '.' and lslfuncs.F32( float(neg + news[:-1] + exp)) == value: news = news[:-1] if len(neg + news) < len(s) and lslfuncs.F32( float(neg + news + exp)) == value: # Success! But we try even harder. We may have converted # 9.9999e3 into 10.e3; that needs to be turned into 1.e4. if exp != '': if news[2:3] == '.': # we converted 9.9... into 10. newexp = 'e' + str( int(exp[1:]) + 1) # increase exponent news2 = news[0] + '.' + news[1] + news[ 3:] # move dot to the left while news2[-1] == '0': # remove trailing zeros news2 = news2[:-1] if len(neg + news2) < len(s) and lslfuncs.F32( float(neg + news2 + newexp)) == value: news = news2 exp = newexp s = neg + news if exp and s[-1] == '.': s = s[:-1] # transfrom e.g. 1.e-30 into 1e-30 if value >= 0 or self.globalmode or not self.optsigns: return s + exp return '((float)' + s + exp + ')' if tvalue == Vector: return '<' + self.Value2LSL(value[0]) + ', ' + self.Value2LSL(value[1]) \ + ', ' + self.Value2LSL(value[2]) + '>' if tvalue == Quaternion: return '<' + self.Value2LSL(value[0]) + ', ' + self.Value2LSL(value[1]) \ + ', ' + self.Value2LSL(value[2]) + ', ' + self.Value2LSL(value[3]) + '>' if tvalue == list: if value == []: return '[]' if len(value) < 5: save_listmode = self.listmode self.listmode = True ret = '[' + self.Value2LSL(value[0]) for elem in value[1:]: ret += ', ' + self.Value2LSL(elem) ret += ']' self.listmode = save_listmode return ret ret = '' if lslcommon.IsCalc else '\n' first = True self.indentlevel += 0 if lslcommon.IsCalc else 1 for entry in value: ret += self.dent() + ('[ ' if first else ', ') save_listmode = self.listmode self.listmode = True ret += self.Value2LSL(entry) + '\n' self.listmode = save_listmode first = False ret += self.dent() self.indentlevel -= 0 if lslcommon.IsCalc else 1 return ret + ']' assert False, u'Value of unknown type in Value2LSL: ' + repr(value)
def test_coverage_misc(self): """Miscellaneous tests that can't be computed or are too difficult to compute with scripts """ sys.stderr.write('\nRunning misc coverage tests: ') # Doesn't accept bytes self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.zstr, b"blah") # Can't typecast float to vector self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslfuncs.F32(1.2), lslcommon.Vector) # Can't typecast integer to vector self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, 1, lslcommon.Vector) # Can't typecast vector to key self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslcommon.Vector((1., 2., 3.)), lslcommon.Key) # Can't typecast quaternion to key self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslcommon.Quaternion((1., 2., 3., 4.)), lslcommon.Key) # Can't typecast list to vector self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, [ 1, 1., lslcommon.Key(u'blah'), lslcommon.Quaternion((1., 0., 0., 0.)) ], lslcommon.Vector) # Can't typecast key to integer self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, lslcommon.Key(u"1"), int) # Can't negate string self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.neg, u"3") # Can't add two keys self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.add, lslcommon.Key(u"1"), lslcommon.Key(u"2")) # Can't subtract two strings self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.sub, u"1", u"2") # Can't multiply two strings self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, u"1", u"2") # Can't multiply quaternion and float in any order self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, lslcommon.Quaternion((1., 2., 3., 4.)), 1.) self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, 1., lslcommon.Quaternion((1., 2., 3., 4.))) # Can't multiply quaternion by vector (but the opposite order is OK) self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, lslcommon.Quaternion((1., 2., 3., 4.)), lslcommon.Vector((1., 2., 3.))) # Can't divide quaternion by vector either self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.div, lslcommon.Quaternion((1., 2., 3., 4.)), lslcommon.Vector((1., 2., 3.))) # Can't mod floats self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mod, 3., 3) # Can't compare string and integer self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.compare, u'3', 4) self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.less, u'3', 4) # Bytes is not a valid type to multiply by (in any order) self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.mul, b"a", 3) self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.mul, lslcommon.Vector((3., 4., 5.)), b"a") self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.typecast, b"", unicode) # v2f/q2f coverage (force conversion from ints to floats) self.assertEqual(repr(lslfuncs.v2f(lslcommon.Vector((1, 0, 0)))), 'Vector((1.0, 0.0, 0.0))') self.assertEqual( repr(lslfuncs.q2f(lslcommon.Quaternion((1, 0, 0, 0)))), 'Quaternion((1.0, 0.0, 0.0, 0.0))') # Key repr coverage self.assertEqual(repr(lslcommon.Key(u'')), "Key(u'')" if str != unicode else "Key('')") # string + key coverage self.assertEqual(lslfuncs.add(u'a', lslcommon.Key(u'b')), u'ab') self.assertEqual(type(lslfuncs.add(u'a', lslcommon.Key(u'b'))), unicode) # The SEF table prevents this assertion from being reachable via script. self.assertRaises(lslfuncs.ELSLCantCompute, lslfuncs.llXorBase64Strings, u"AABA", u"AABA") self.assertRaises(lslfuncs.ELSLCantCompute, lslfuncs.llModPow, 3, 5, 7) # Check invalid type in llGetListEntryType self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.llGetListEntryType, [b'a'], 0) # Check that Value2LSL raises an exception if the type is unknown. outmod = lsloutput.outscript() # Script with a single node of type Expression, containing a constant # of type Bytes. That's rejected by the output module. msg = None script = [ nr(nt='EXPR', t='string', ch=[nr(nt='CONST', t='string', value=b'ab')]) ] save_IsCalc = lslcommon.IsCalc lslcommon.IsCalc = True try: try: outmod.output((script, ())) except AssertionError as e: msg = str(e) finally: lslcommon.IsCalc = save_IsCalc self.assertEqual( msg, u"Value of unknown type in Value2LSL: 'ab'" if python2 else u"Value of unknown type in Value2LSL: b'ab'") del msg # Extended assignment in output script = [ nr(nt='EXPR', t='integer', ch=[ nr(nt='^=', t='integer', ch=[ nr(nt='IDENT', t='integer', name='a', scope=0), nr(nt='CONST', t='integer', value=3) ]) ]) ] save_IsCalc = lslcommon.IsCalc lslcommon.IsCalc = True try: out = outmod.output((script, [{ 'a': { 'Kind': 'v', 'Loc': 1, 'Scope': 0, 'Type': 'integer' } }])) finally: lslcommon.IsCalc = save_IsCalc self.assertEqual(out, 'a = a ^ (3)') del out, script, outmod, save_IsCalc