Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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