Merge branch 'jsbuilt-ins' into jsinterp

This commit is contained in:
sulyi 2018-06-01 03:13:45 +02:00
commit b856d55847
11 changed files with 1060 additions and 0 deletions

View File

@ -0,0 +1,12 @@
from __future__ import unicode_literals
skip = {'parse': 'Ast not yet implemented'}
tests = [
{
'code': '"hello".split("");',
'globals': {},
'asserts': [{'value': ['h', 'e', 'l', 'l', 'o']}],
'ast': []
}
]

View File

@ -0,0 +1,57 @@
from __future__ import unicode_literals
from . import base
from . import jsobject
from . import jsfunction
from . import jsarray
from . import jsboolean
from . import jsstring
from . import jsnumber
from .base import null, undefined
from .jsboolean import false, true
def _eval(code):
pass
def _parse_int(string, radix):
pass
def _parse_float(string):
pass
def _is_nan(number):
pass
def _is_infinite(number):
pass
def _decode_uri(encoded_uri):
pass
def _decode_uri_component(encoded_uri_component):
pass
def _encode_uri(uri):
pass
def _encode_uri_component(uri_component):
pass
global_obj = jsobject.JSObject.construct(
{'Object': jsobject.JSObject(),
'Array': jsarray.JSArray(),
'Function': jsfunction.JSFunction(),
'String': jsstring.JSString(),
'Number': jsnumber.JSNumber()
})

View File

@ -0,0 +1,65 @@
from __future__ import unicode_literals
from types import FunctionType
from ...compat import compat_str
class JSBase(object):
def __init__(self, name):
self.name = name
self.props = {}
own = {}
undefined = JSBase('undefined')
null = JSBase('null')
class JSProtoBase(JSBase):
def __init__(self):
super(JSProtoBase, self).__init__('')
cls = self.__class__
while cls.__base__ is not JSProtoBase:
cls = cls.__base__
props = cls.own.copy()
props.update(self.props)
self.props = props
self.value = {}
def get_prop(self, prop):
result = self.value.get(prop) if hasattr(self.value, 'get') else None
if result is None:
result = self.own.get(prop)
if result is None:
result = self.props.get(prop)
return result
def call_prop(self, prop, *args, **kwargs):
func = self.get_prop(prop)
if isinstance(func, native_function):
return func(self, *args, **kwargs)
elif isinstance(func, staticmethod):
return func.__func__(*args, **kwargs)
elif isinstance(func, classmethod):
return func.__func__(self.__class__, *args, **kwargs)
elif isinstance(func, JSBase) and hasattr(func, 'call'):
return func.call(*args, **kwargs)
else:
# FIXME instead of prop should return the whole expression
# needs to use internal exception
# interpreter should raise JSTypeError
raise Exception('TypeError: %s is not a function' % prop)
jsclass = ''
native_bool = bool
native_string = compat_str
native_number = (int, float)
native_object = dict
native_array = (list, tuple)
native_function = FunctionType

View File

@ -0,0 +1,222 @@
from __future__ import unicode_literals
import re
from math import isnan, isinf, log10
from sys import float_info
from .base import native_bool, native_string, native_number, native_object
from .utils import to_js
from ..jsgrammar import __HEXADECIMAL_RE
undefined_type = object()
null_type = object()
boolean_type = object()
string_type = object()
number_type = object()
object_type = object()
def jstype(o):
from .base import null, undefined
from .jsboolean import true, false
if o is undefined:
return undefined_type
elif o is None or o is null:
return null_type
elif isinstance(o, native_bool) or o is true or o is false:
return boolean_type
elif isinstance(o, native_string):
return string_type
elif isinstance(o, native_number):
return number_type
elif isinstance(o, native_object):
return object_type
return None
def to_primitive(o, hint=None):
# TODO to_primitive
return o
def to_boolean(o):
from .base import undefined, null
from .jsobject import JSObjectPrototype
from .jsboolean import JSBooleanPrototype, false, true
from .jsstring import JSStringPrototype
from .jsnumber import JSNumberPrototype
if o is undefined or o is null:
return false
elif isinstance(o, JSBooleanPrototype):
return o.value
elif isinstance(o, JSNumberPrototype):
return true if o.value and not isnan(o.value) else false
elif isinstance(o, JSStringPrototype):
return true if o.value else false
elif isinstance(o, JSObjectPrototype):
return true
else:
raise Exception('Failed to convert type %s to Boolean (not specified)' % type(o))
def to_number(o):
from .base import null, undefined
from .jsobject import JSObjectPrototype
from .jsboolean import JSBooleanPrototype, false, true
from .jsstring import JSStringPrototype
if o is undefined:
return float('nan')
elif o is null or isinstance(o, JSBooleanPrototype) and o.value is false:
return 0
elif isinstance(o, JSBooleanPrototype) and o.value is true:
return 1
elif isinstance(o, JSStringPrototype):
_STR_FLOAT_RE = r'(?:(?:[0-9]+(?:\.[0-9]*)?)|(?:\.[0-9]+))(?:[eE][+-]?[0-9]+)?'
m = re.match(r'^[\s\n]*(?P<value>(?:[+-]*(?:Infinity|%(float)s))|%(hex)s)?[\s\n]*$' % {'float': _STR_FLOAT_RE,
'hex': __HEXADECIMAL_RE},
o.value)
if m:
v = m.group('value')
if v:
s = 1 if v.startswith('+') or v.startswith('-') else 0
if v[s:] == 'Infinity':
return float(v[:s] + 'inf') # 10 ** 10000 according to spec
elif v[s:].isdigit():
return int(v)
elif v.startswith('0x') or v.startswith('0X'):
return int(v, 16)
else:
return float(v)
else:
return 0
else:
return float('nan')
elif isinstance(o, JSObjectPrototype):
prim_value = to_primitive(o, 'Number')
return to_number(prim_value)
else:
raise Exception('Failed to convert type %s to Number (not specified)' % type(o))
def to_integer(o):
number = to_number(o)
if isnan(number):
return 0
elif isinf(number) or number == 0:
return number
return int(number) # equivalent to: int(copysign(floor(abs(number)), number))
def to_int32(o):
number = to_number(o)
if isnan(number) or isinf(number) or number == 0:
return 0
pos_int = int(number)
int32 = pos_int % 2 ** 32
return int32 if int32 < 2 ** 31 else int32 - 2 ** 32
def to_uint32(o):
number = to_number(o)
if isnan(number) or isinf(number) or number == 0:
return 0
pos_int = int(number)
int32 = pos_int % 2 ** 32
return int32
def to_uint16(o):
number = to_number(o)
if isnan(number) or isinf(number) or number == 0:
return 0
pos_int = int(number)
int16 = pos_int % 2 ** 16
return int16
def to_string(o):
from .base import null, undefined
from .jsobject import JSObjectPrototype
from .jsboolean import JSBooleanPrototype, false, true
from .jsnumber import JSNumberPrototype
if o is undefined:
return 'undefined'
elif o is null:
return 'null'
elif isinstance(o, JSBooleanPrototype):
if o is true:
return 'true'
elif o is false:
return 'false'
elif isinstance(o, JSNumberPrototype):
ov = o.value
if isnan(ov):
return 'NaN'
elif ov == 0.0:
return '0'
elif ov < 0:
return '-' + to_string(to_js(-ov))
elif isinf(ov):
return 'Infinity'
else:
# numerically unstable example: 3333330000000000000.3 or 3.3333300000000000003e+20
n = log10(ov) + 1
n = int(n)
k = 1
while True:
exp = 10 ** (k - n)
s = int(ov * exp)
if abs(ov * exp - s) < float_info.epsilon:
break
k += 1
if s % 10 == 0:
s //= 10
m = '%d' % s
if k <= n <= 21:
return m[:k] + '0' * (n - k)
elif 0 < n <= 21:
return m[:n] + '.' + m[n:k]
elif -6 < n <= 0:
return '0.' + '0' * -n + m[:k]
elif k == 1:
return m[0] + 'e%+d' % (n - 1)
else:
return m[0] + '.' + m[:k] + 'e%+d' % (n - 1)
elif isinstance(o, JSObjectPrototype):
prim_value = to_primitive(o, 'String')
return to_string(prim_value)
else:
raise Exception('Failed to convert type %s to String (not specified)' % type(o))
def to_object(o):
from .base import null, undefined
from .jsobject import JSObjectPrototype
from .jsboolean import JSBooleanPrototype
from .jsstring import JSStringPrototype
from .jsnumber import JSNumberPrototype
if o is undefined or o is null:
raise Exception('TypeError: Cannot convert undefined or null to object')
elif isinstance(o, JSBooleanPrototype):
return JSBooleanPrototype(o)
elif isinstance(o, JSNumberPrototype):
return JSNumberPrototype(o)
elif isinstance(o, JSStringPrototype):
return JSStringPrototype(o)
elif isinstance(o, JSObjectPrototype):
return o
def throw_type_error():
# TODO [[ThrowTypeError]] (13.2.3)
pass

View File

@ -0,0 +1,153 @@
from __future__ import unicode_literals
from .base import native_number, undefined
from .jsobject import JSObject, JSObjectPrototype
from .jsnumber import JSNumberPrototype
class JSArrayPrototype(JSObjectPrototype):
def __init__(self, value=None):
super(JSArrayPrototype, self).__init__()
if value is None:
# prototype
self.value = []
else:
self.value = value
self.own = dict((str(i), v) for i, v in enumerate(value))
self.own['length'] = self._length
def __str__(self):
return 'JSArrayPrototype: %s' % self.value
def __repr__(self):
return 'JSArrayPrototype(%s, %s)' % (self.value, self._length)
@property
def _length(self):
return len(self.value)
@staticmethod
def _constructor(*args):
return JSArray.construct(*args)
def _to_string(self):
return 'array to string'
def _to_locale_string(self):
return 'array to locale string'
def _concat(self, *items):
return 'array concat'
def _join(self, sep):
return 'array join'
def _pop(self):
return 'array pop'
def _push(self, *items):
return 'array push'
def _reverse(self):
return 'array reverse'
def _shift(self):
return 'array shift'
def _slice(self, start, end):
return 'array slice'
def _sort(self, cmp):
return 'array sort'
def _splice(self, start, delete_count, *items):
return 'array splice'
def _unshift(self, *items):
return 'array unshift'
def _index_of(self, elem, from_index=0):
return 'array index of'
def _last_index_of(self, elem, from_index=None):
if from_index is None:
from_index = len(self.value) - 1
return 'array index of'
def _every(self, callback, this_arg=None):
return 'array every'
def _some(self, callback, this_arg=None):
return 'array some'
def _for_each(self, callback, this_arg=None):
return 'array for_each'
def _map(self, callback, this_arg=None):
return 'array map'
def _filter(self, callback, this_arg=None):
return 'array filter'
def _reduce(self, callback, init=None):
return 'array reduce'
def _reduce_right(self, callback, init=None):
return 'array reduce right'
jsclass = 'Array'
own = {
'length': _length,
'constructor': _constructor,
'toString': _to_string,
'toLocaleString': _to_locale_string,
'concat': _concat,
'join': _join,
'pop': _pop,
'push': _push,
'reverse': _reverse,
'shift': _shift,
'slice': _slice,
'sort': _sort,
'splice': _splice,
'unshift': _unshift,
'indexOf': _index_of,
'lastIndexOf': _last_index_of,
'every': _every,
'some': _some,
'forEach': _for_each,
'map': _map,
'filter': _filter,
'reduce': _reduce,
'reduceRight': _reduce_right
}
class JSArray(JSObject):
@staticmethod
def call(*args):
return JSArray.construct(*args)
@staticmethod
def construct(*args):
if len(args) == 1:
if isinstance(args[0], native_number):
return JSArrayPrototype([undefined] * args[0])
elif isinstance(args[0], JSNumberPrototype):
return JSArrayPrototype([undefined] * args[0]._value_of())
if args:
return JSArrayPrototype(args)
else:
return JSArrayPrototype([])
def _is_array(self, arg):
return 'array is array'
name = JSArrayPrototype.jsclass
own = {
'length': 1,
'prototype': JSArrayPrototype(),
'isArray': _is_array
}

View File

@ -0,0 +1,62 @@
from __future__ import unicode_literals
from .internals import jstype, boolean_type, object_type, to_boolean
from .utils import to_js
from .jsobject import JSObject, JSObjectPrototype
class JSBooleanPrototype(JSObjectPrototype):
def __init__(self, value=None):
super(JSBooleanPrototype, self).__init__()
if value is None:
# prototype
value = False
else:
self.value = value
self.own = {}
@staticmethod
def _constructor(value=None):
return JSBoolean.construct(value)
def _to_string(self):
# TODO find way to test it in other interpreters
if jstype(self) is boolean_type:
b = self
elif jstype(self) is object_type and self.jsclass == 'Boolean':
b = self.value
else:
raise Exception('TypeError')
return 'true' if b is true else 'false'
def _value_of(self):
return 'boolean value of'
jsclass = 'Boolean'
own = {
'constructor': _constructor,
'toString': _to_string,
'valueOf': _value_of
}
true = JSBooleanPrototype(True)
false = JSBooleanPrototype(False)
class JSBoolean(JSObject):
@staticmethod
def call(value=None):
return to_boolean(to_js(value))
@staticmethod
def construct(value=None):
return JSBooleanPrototype(to_boolean(to_js(value)))
name = JSBooleanPrototype.jsclass
own = {
'length': 1,
'prototype': JSBooleanPrototype()
}

View File

@ -0,0 +1,110 @@
from __future__ import unicode_literals
from .base import undefined, null
from .internals import to_string, throw_type_error
from .base import native_function, JSBase
from .utils import to_js
from .jsobject import JSObject, JSObjectPrototype
class JSFunctionPrototype(JSObjectPrototype):
def __init__(self, name, body, formal_args):
if name is None and body is None and formal_args is None:
# prototype
super(JSFunctionPrototype, self).__init__()
self.f_name = ''
self.body = ''
else:
if isinstance(body, JSBase):
super(JSFunctionPrototype, self).__init__(body.own)
self.body = '[native code]'
elif isinstance(body, native_function):
super(JSFunctionPrototype, self).__init__()
self.body = '[native code]'
else:
super(JSFunctionPrototype, self).__init__()
body = to_js(body)
self.body = to_string(body) if body is not undefined or body is not null else ''
self.f_name = name
self.arguments = list(formal_args)
proto = JSObject.construct()
proto.own['constructor'] = self
self.own = {'length': self._length,
'prototype': proto
}
# TODO Handle strict mode
strict = True
if strict:
thrower = throw_type_error
self.own['caller'] = thrower
self.own['arguments'] = thrower
# FIXME: JSProtoBase sets body to '' instead of None
# TODO check if self._args can be parsed as formal parameter list
# TODO check if self._body can be parsed as function body
# TODO set strict
# TODO throw strict mode exceptions
# (double argument, "eval" or "arguments" in arguments, function identifier is "eval" or "arguments")
@property
def _length(self):
return len(self.arguments)
@staticmethod
def _constructor(arguments=None):
return JSFunction.construct(arguments)
def _to_string(self):
if self.body is not None:
body = '\n'
body += '\t' + self.body if self.body else self.body
else:
body = ''
return 'function %s(%s) {%s\n}' % (
self.f_name,
', '.join(self.arguments),
body)
def _apply(self, this_arg, arg_array):
return 'function apply'
def _call(self, this_arg, *args):
return 'function call'
def _bind(self, this_arg, *args):
return 'function bind'
jsclass = 'Function'
own = {
'length': 0,
'constructor': _constructor,
'toString': _to_string,
'apply': _apply,
'call': _call,
'bind': _bind
}
class JSFunction(JSObject):
@staticmethod
def call(formal_args=None):
return JSFunction.construct(formal_args)
@staticmethod
def construct(formal_args=None):
if formal_args is not None and formal_args:
body = formal_args[-1]
formal_args = []
for arg in formal_args[:-1]:
formal_args.extend(a.strip() for a in arg.split(','))
else:
body = ''
formal_args = []
return JSFunctionPrototype('anonymous', body, formal_args)
name = JSFunctionPrototype.jsclass
own = {
'length': 1,
'prototype': JSFunctionPrototype(None, None, None)
}

View File

@ -0,0 +1,74 @@
from __future__ import unicode_literals
from .internals import jstype, number_type, to_number
from .utils import to_js
from .jsobject import JSObject, JSObjectPrototype
class JSNumberPrototype(JSObjectPrototype):
def __init__(self, value=None):
super(JSNumberPrototype, self).__init__()
if value is None:
# prototype
value = 0
else:
self.value = value
self.own = {}
@staticmethod
def _constructor(value=None):
return JSNumber.construct(value)
def _to_string(self, radix=None):
pass
def _to_locale_string(self):
pass
def _value_of(self):
if jstype(self.value) is not number_type or isinstance(self.value, JSNumberPrototype):
# TODO find way to test it in other interpreters
raise Exception('TypeError')
return self.value
def _to_fixed(self, frac_digits):
return 'Number toFixed'
def _to_exponential(self, frac_digits):
return 'Number toExponential'
def _to_precision(self, prec):
return 'Number toPrecision'
jsclass = 'Number'
own = {
'constructor': _constructor,
'toString': _to_string,
'toLocaleString': _to_locale_string,
'valueOf': _value_of,
'toFixed': _to_fixed,
'toExponential': _to_exponential,
'toPrecision': _to_precision
}
class JSNumber(JSObject):
@staticmethod
def call(value=None):
return to_number(to_js(value)) if value is not None else 0
@staticmethod
def construct(value=None):
return JSNumberPrototype(to_number(to_js(value)) if value is not None else 0)
name = JSNumberPrototype.jsclass
own = {
'length': 1,
'prototype': JSNumberPrototype(),
'MAX_VALUE': 1.7976931348623157 * 10 ** 308,
'MIN_VALUE': 5 * 10 ** (-324),
'NAN': float('nan'),
'NEGATIVE_INFINITY': float('-inf'),
'POSITIVE_INFINITY': float('inf'),
}

View File

@ -0,0 +1,133 @@
from __future__ import unicode_literals
from .base import JSProtoBase, JSBase, null, undefined
from .utils import to_js, js
from .internals import to_object
class JSObjectPrototype(JSProtoBase):
def __init__(self, value=None):
super(JSObjectPrototype, self).__init__()
if value is not None:
self.props.update(self.own)
self.own = self.value = value
@staticmethod
def _constructor(value=None):
return JSObject.construct(value)
def _to_string(self):
return 'object to string'
def _to_locale_string(self):
return 'object to locale string'
def _value_of(self):
return 'object value of'
def _has_own_property(self, v):
return v in self.own
def _is_prototype_of(self, v):
return 'object has own prop'
def _is_property_enumerable(self, v):
return 'object is property enumerable'
jsclass = 'Object'
own = {
'constructor': _constructor,
'toString': _to_string,
'toLocaleString': _to_locale_string,
'valueOf': _value_of,
'hasOwnProperty': _has_own_property,
'isPrototypeOf': _is_prototype_of,
'propertyIsEnumerable': _is_property_enumerable
}
class JSObject(JSBase):
def __init__(self):
super(JSObject, self).__init__(self.name)
@staticmethod
def call(value=None):
if value is null or value is undefined or value is None:
return JSObject.construct(value)
return to_object(to_js(value))
@staticmethod
def construct(value=None):
from .jsboolean import JSBooleanPrototype
from .jsnumber import JSNumberPrototype
from .jsstring import JSStringPrototype
value = to_js(value)
# TODO set [[Prototype]], [[Class]], [[Extensible]], internal methods
if value is undefined or value is null:
return JSObjectPrototype({})
elif isinstance(value, JSObjectPrototype):
return value
elif isinstance(value, (JSStringPrototype, JSNumberPrototype, JSBooleanPrototype)):
return to_object(value)
def _get_prototype_of(self, o):
return 'object get prototype of'
def _get_own_property_descriptor(self, o, p):
return 'object desc'
@js
def _get_own_property_names(self, o):
return list(o.own.keys())
def _create(self, o, props=None):
return 'object create'
def _define_property(self, o, p, attr):
return 'object define prop'
def _define_properties(self, o, props):
return 'object define properties'
def _seal(self, o):
return 'object seal'
def _freeze(self, o):
return 'object freeze'
def _prevent_extensions(self, o):
return 'object prevent extension'
def _is_sealed(self, o):
return 'object is sealed'
def _is_frozen(self, o):
return 'object is frozen'
def _is_extensible(self, o):
return 'object is extensible'
def _keys(self, o):
return 'object keys'
name = JSObjectPrototype.jsclass
own = {
'length': 1,
'prototype': JSObjectPrototype(),
'getPrototypeOf': _get_prototype_of,
'getOwnPropertyDescriptor': _get_own_property_descriptor,
'getOwnPropertyNames': _get_own_property_names,
'create': _create,
'defineProperty': _define_property,
'defineProperties': _define_properties,
'seal': _seal,
'freeze': _freeze,
'preventExtensions': _prevent_extensions,
'isSealed': _is_sealed,
'isFrozen': _is_frozen,
'isExtensible': _is_extensible,
'keys': _keys
}

View File

@ -0,0 +1,127 @@
from __future__ import unicode_literals
from .internals import to_string
from .jsobject import JSObject, JSObjectPrototype
class JSStringPrototype(JSObjectPrototype):
def __init__(self, value=None):
super(JSStringPrototype, self).__init__()
if value is None:
# prototype
value = ''
else:
self.value = value
self.own = {'length': self._length}
@property
def _length(self):
return len(self.value)
@staticmethod
def _constructor(value=None):
return JSString.construct(value)
def _to_string(self):
return self.value
def _value_of(self):
return self.value
def _char_at(self, pos):
return 'string char at'
def _char_code_at(self, pos):
return 'string char code at'
def _concat(self, *args):
return 'string concat'
def _index_of(self, search, pos):
return 'string index of'
def _last_index_of(self, search, pos):
return 'string last index of'
def _locale_compare(self, that):
return 'string locale compare'
def _match(self, regexp):
return 'string match'
def _replace(self, search, value):
return 'string replace'
def _search(self, regexp):
return 'string search'
def _slice(self, start, end):
return 'string slice'
def _split(self, sep):
return 'string split'
def _substring(self, start, end):
return 'string substring'
def _to_lower_case(self):
return 'string to lower case'
def _to_local_lower_case(self):
return 'string to local lower case'
def _to_upper_case(self):
return 'string to upper case'
def _to_local_upper_case(self):
return 'string to local upper case'
def _trim(self):
return 'string trim'
jsclass = 'String'
own = {
'length': _length,
'constructor': _constructor,
'toString': _to_string,
'valueOf': _value_of,
'charAt': _char_at,
'charCodeAt': _char_code_at,
'concat': _concat,
'indexOf': _index_of,
'lastIndexOf': _last_index_of,
'localeCompare': _locale_compare,
'match': _match,
'replace': _replace,
'search': _search,
'slice': _slice,
'split': _split,
'substring': _substring,
'toLowerCase': _to_lower_case,
'toLocalLowerCase': _to_local_lower_case,
'toUpperCase': _to_upper_case,
'toLocalUpperCase': _to_local_upper_case,
'trim': _trim
}
class JSString(JSObject):
@staticmethod
def call(value=None):
return '' if value is None else to_string(value)
@staticmethod
def construct(value=None):
return JSStringPrototype('' if value is None else to_string(value))
def _from_char_code(self, *args):
return 'String from char code'
name = JSStringPrototype.jsclass
own = {
'length': 1,
'prototype': JSStringPrototype(),
'fromCharCode': _from_char_code
}

View File

@ -0,0 +1,45 @@
from .base import (
JSProtoBase, native_bool, native_string, native_number, native_object, native_function, JSBase, native_array
)
def _get_formal_args(func):
return func.__code__.co_varnames[func.__code__.co_argcount - len((func.__defaults__))]
def to_js(o, name=None):
from .base import undefined
from .jsarray import JSArrayPrototype
from .jsboolean import JSBooleanPrototype
from .jsfunction import JSFunctionPrototype
from .jsnumber import JSNumberPrototype
from .jsobject import JSObjectPrototype
from .jsstring import JSStringPrototype
if isinstance(o, JSProtoBase):
return o
elif o is None:
return undefined
elif isinstance(o, native_bool):
return JSBooleanPrototype(o)
elif isinstance(o, native_string):
return JSStringPrototype(o)
elif isinstance(o, native_number):
return JSNumberPrototype(o)
elif isinstance(o, native_object):
return JSObjectPrototype(o)
elif isinstance(o, native_function):
return JSFunctionPrototype(name, o, _get_formal_args(o))
elif isinstance(o, JSBase) and hasattr(o, 'call'):
return JSFunctionPrototype(o.name, o, _get_formal_args(o.call))
elif isinstance(o, native_array):
return JSArrayPrototype(o)
else:
raise Exception('Not allowed conversion %s to js' % type(o))
def js(func):
def wrapper(*args, **kwargs):
return to_js(*func(*args, **kwargs))
return wrapper