Implement NotImplemented, fallback operators

Adds 'NOTIMPL' as a new primitive value, available from
__builtins__.NotImplemented. Adds support for inverse /
reflected overrides for binary operators. Adds opcodes
for LESS_EQUAL and GREATER_EQUAL.
This commit is contained in:
K. Lange 2021-03-30 16:42:29 +09:00
parent 873daa1f67
commit 70d5f1b2b7
12 changed files with 257 additions and 77 deletions

View File

@ -527,9 +527,9 @@ static void compareChained(int inner) {
case TOKEN_BANG_EQUAL: emitBytes(OP_EQUAL, OP_NOT); break;
case TOKEN_EQUAL_EQUAL: emitByte(OP_EQUAL); break;
case TOKEN_GREATER: emitByte(OP_GREATER); break;
case TOKEN_GREATER_EQUAL: emitBytes(OP_LESS, OP_NOT); break;
case TOKEN_GREATER_EQUAL: emitByte(OP_GREATER_EQUAL); break;
case TOKEN_LESS: emitByte(OP_LESS); break;
case TOKEN_LESS_EQUAL: emitBytes(OP_GREATER, OP_NOT); break;
case TOKEN_LESS_EQUAL: emitByte(OP_LESS_EQUAL); break;
case TOKEN_IS: emitByte(OP_IS); if (invert) emitByte(OP_NOT); break;

View File

@ -68,7 +68,9 @@ typedef enum {
OP_ANNOTATE,
OP_BEGIN_FINALLY,
OP_END_FINALLY,
/* current highest: 45 */
OP_GREATER_EQUAL,
OP_LESS_EQUAL,
/* current highest: 47 */
OP_CALL = 64,
OP_CLASS,

View File

@ -29,6 +29,7 @@ typedef enum {
KRK_VAL_NONE = 0xFFFF,
KRK_VAL_KWARGS = 0x7FFC,
KRK_VAL_OBJECT = 0x7FFD,
KRK_VAL_NOTIMPL = 0x7FFE,
} KrkValueType;
#define KRK_VAL_MASK_BOOLEAN ((uint64_t)0xFFFC000000000000) /* 1..1100 */
@ -37,6 +38,7 @@ typedef enum {
#define KRK_VAL_MASK_NONE ((uint64_t)0xFFFF000000000000) /* 1..1111 */
#define KRK_VAL_MASK_KWARGS ((uint64_t)0x7FFC000000000000) /* 0..1100 */
#define KRK_VAL_MASK_OBJECT ((uint64_t)0x7FFD000000000000) /* 0..1101 */
#define KRK_VAL_MASK_NOTIMPL ((uint64_t)0x7FFE000000000000) /* 0..1110 */
#define KRK_VAL_MASK_NAN ((uint64_t)0x7FFC000000000000)
#define KRK_VAL_MASK_LOW ((uint64_t)0x0000FFFFFFFFFFFF)
@ -171,6 +173,7 @@ typedef union {
} KrkValueDbl;
#define NONE_VAL(value) ((KrkValue)(KRK_VAL_MASK_LOW | KRK_VAL_MASK_NONE))
#define NOTIMPL_VAL(value) ((KrkValue)(KRK_VAL_MASK_LOW | KRK_VAL_MASK_NOTIMPL))
#define BOOLEAN_VAL(value) ((KrkValue)((uint32_t)(value) | KRK_VAL_MASK_BOOLEAN))
#define INTEGER_VAL(value) ((KrkValue)((uint32_t)(value) | KRK_VAL_MASK_INTEGER))
#define KWARGS_VAL(value) ((KrkValue)((uint32_t)(value) | KRK_VAL_MASK_KWARGS))
@ -182,6 +185,7 @@ typedef union {
#define AS_BOOLEAN(value) ((krk_integer_type)((value) & KRK_VAL_MASK_LOW))
#define AS_INTEGER(value) ((krk_integer_type)((value) & KRK_VAL_MASK_LOW))
#define AS_NOTIMPL(value) ((krk_integer_type)((value) & KRK_VAL_MASK_LOW))
#define AS_HANDLER(value) ((uint32_t)((value) & KRK_VAL_MASK_LOW))
#define AS_OBJECT(value) ((KrkObj*)(uintptr_t)((value) & KRK_VAL_MASK_LOW))
#define AS_FLOATING(value) (((KrkValueDbl){.val = (value)}).dbl)
@ -192,6 +196,7 @@ typedef union {
#define IS_HANDLER(value) (((value) & KRK_VAL_MASK_NONE) == KRK_VAL_MASK_HANDLER)
#define IS_OBJECT(value) (((value) & KRK_VAL_MASK_NONE) == KRK_VAL_MASK_OBJECT)
#define IS_KWARGS(value) (((value) & KRK_VAL_MASK_NONE) == KRK_VAL_MASK_KWARGS)
#define IS_NOTIMPL(value) (((value) & KRK_VAL_MASK_NONE) == KRK_VAL_MASK_NOTIMPL)
#define IS_FLOATING(value) (((value) & KRK_VAL_MASK_NAN) != KRK_VAL_MASK_NAN)
#define AS_HANDLER_TYPE(value) (AS_HANDLER(value) >> 16)

View File

@ -165,6 +165,7 @@ struct BaseClasses {
KrkClass * propertyClass; /**< Magic object that calls a function when accessed from an instance through the dot operator. */
KrkClass * codeobjectClass; /**< Static compiled bytecode container (KrkCodeObject) */
KrkClass * generatorClass; /**< Generator object. */
KrkClass * notImplClass; /**< NotImplementedType */
};
/**

View File

@ -94,6 +94,12 @@ KRK_METHOD(NoneType,__str__,{
return OBJECT_VAL(S("None"));
})
#define IS_NotImplementedType(o) IS_NOTIMPL(o)
#define AS_NotImplementedType(o) (1)
KRK_METHOD(NotImplementedType,__str__,{
return OBJECT_VAL(S("NotImplemented"));
})
#undef BIND_METHOD
#define BIND_METHOD(klass,method) do { krk_defineNative(& _ ## klass->methods, #method, _ ## klass ## _ ## method); } while (0)
_noexport
@ -128,4 +134,11 @@ void _createAndBind_numericClasses(void) {
BIND_METHOD(NoneType, __str__);
krk_defineNative(&_NoneType->methods, "__repr__", FUNC_NAME(NoneType,__str__));
krk_finalizeClass(_NoneType);
KrkClass * _NotImplementedType = ADD_BASE_CLASS(vm.baseClasses->notImplClass, "NotImplementedType", vm.baseClasses->objectClass);
BIND_METHOD(NotImplementedType, __str__);
krk_defineNative(&_NotImplementedType->methods, "__repr__", FUNC_NAME(NotImplementedType,__str__));
krk_finalizeClass(_NotImplementedType);
krk_attachNamedValue(&vm.builtins->fields, "NotImplemented", NOTIMPL_VAL());
}

View File

@ -434,45 +434,27 @@ KRK_METHOD(str,rstrip,{
return _string_strip_shared(argc,argv,2);
})
KRK_METHOD(str,__lt__,{
METHOD_TAKES_EXACTLY(1);
if (!IS_STRING(argv[1])) {
return KWARGS_VAL(0); /* represents 'not implemented' */
}
if (AS_STRING(argv[0]) == AS_STRING(argv[1])) return BOOLEAN_VAL(0);
#define strCompare(name,lop,iop,rop) \
KRK_METHOD(str,name,{ \
METHOD_TAKES_EXACTLY(1); \
if (!IS_STRING(argv[1])) { \
return NOTIMPL_VAL(); \
} \
size_t aLen = AS_STRING(argv[0])->length; \
size_t bLen = AS_STRING(argv[1])->length; \
const char * a = AS_CSTRING(argv[0]); \
const char * b = AS_CSTRING(argv[1]); \
for (size_t i = 0; i < (aLen < bLen) ? aLen : bLen; i++) { \
if (a[i] lop b[i]) return BOOLEAN_VAL(1); \
if (a[i] iop b[i]) return BOOLEAN_VAL(0); \
} \
return BOOLEAN_VAL((aLen rop bLen)); \
})
size_t aLen = AS_STRING(argv[0])->length;
size_t bLen = AS_STRING(argv[1])->length;
const char * a = AS_CSTRING(argv[0]);
const char * b = AS_CSTRING(argv[1]);
for (size_t i = 0; i < (aLen < bLen) ? aLen : bLen; i++) {
if (a[i] < b[i]) return BOOLEAN_VAL(1);
if (a[i] > b[i]) return BOOLEAN_VAL(0);
}
return BOOLEAN_VAL((aLen < bLen));
})
KRK_METHOD(str,__gt__,{
METHOD_TAKES_EXACTLY(1);
if (!IS_STRING(argv[1])) {
return KWARGS_VAL(0); /* represents 'not implemented' */
}
if (AS_STRING(argv[0]) == AS_STRING(argv[1])) return BOOLEAN_VAL(0);
size_t aLen = AS_STRING(argv[0])->length;
size_t bLen = AS_STRING(argv[1])->length;
const char * a = AS_CSTRING(argv[0]);
const char * b = AS_CSTRING(argv[1]);
for (size_t i = 0; i < (aLen < bLen) ? aLen : bLen; i++) {
if (a[i] < b[i]) return BOOLEAN_VAL(0);
if (a[i] > b[i]) return BOOLEAN_VAL(1);
}
return BOOLEAN_VAL((aLen > bLen));
})
strCompare(__gt__,>,<,>)
strCompare(__lt__,<,>,<)
strCompare(__ge__,>,<,>=)
strCompare(__le__,<,>,<=)
/** TODO but throw a more descriptive error for now */
KRK_METHOD(str,__mod__,{
@ -903,6 +885,8 @@ void _createAndBind_strClass(void) {
BIND_METHOD(str,__contains__);
BIND_METHOD(str,__lt__);
BIND_METHOD(str,__gt__);
BIND_METHOD(str,__le__);
BIND_METHOD(str,__ge__);
BIND_METHOD(str,__mod__);
BIND_METHOD(str,__repr__);
BIND_METHOD(str,__str__);

View File

@ -42,6 +42,8 @@ SIMPLE(OP_YIELD)
SIMPLE(OP_ANNOTATE)
SIMPLE(OP_BEGIN_FINALLY)
SIMPLE(OP_END_FINALLY)
SIMPLE(OP_GREATER_EQUAL)
SIMPLE(OP_LESS_EQUAL)
CONSTANT(OP_DEFINE_GLOBAL,(void)0)
CONSTANT(OP_CONSTANT,(void)0)
CONSTANT(OP_GET_GLOBAL,(void)0)

102
src/vm.c
View File

@ -463,6 +463,8 @@ inline KrkClass * krk_getType(KrkValue of) {
return vm.baseClasses->boolClass;
case KRK_VAL_NONE:
return vm.baseClasses->noneTypeClass;
case KRK_VAL_NOTIMPL:
return vm.baseClasses->notImplClass;
case KRK_VAL_OBJECT:
switch (AS_OBJECT(of)->type) {
case KRK_OBJ_CLASS:
@ -1379,32 +1381,56 @@ const char * krk_typeName(KrkValue value) {
return krk_getType(value)->name->chars;
}
static KrkValue tryBind(const char * name, KrkValue a, KrkValue b, const char * operator, const char * msg) {
krk_push(b);
krk_push(a);
KrkClass * type = krk_getType(a);
static KrkValue tryBind(const char * name, KrkValue a, KrkValue b, const char * operator, const char * msg, const char * inverse) {
krk_currentThread.scratchSpace[0] = a;
krk_currentThread.scratchSpace[1] = b;
/* Potential return value */
KrkValue value = NONE_VAL();
KrkString * methodName = krk_copyString(name, strlen(name));
krk_push(OBJECT_VAL(methodName));
KrkValue value = KWARGS_VAL(0);
krk_swap(1);
/* Bind from a */
KrkClass * type = krk_getType(a);
krk_push(a);
if (krk_bindMethod(type, methodName)) {
krk_swap(1);
krk_pop();
krk_swap(1);
krk_push(b);
value = krk_callSimple(krk_peek(1), 1, 1);
}
if (IS_KWARGS(value)) {
return krk_runtimeError(vm.exceptions->typeError, msg, operator, krk_typeName(a), krk_typeName(b));
if (!IS_NOTIMPL(value)) goto _success;
krk_pop(); /* name */
} else {
return value;
krk_pop(); /* a */
krk_pop(); /* name */
}
/* Bind from b */
methodName = krk_copyString(inverse, strlen(inverse));
krk_push(OBJECT_VAL(methodName));
type = krk_getType(b);
krk_push(b);
if (krk_bindMethod(type, methodName)) {
krk_push(a);
value = krk_callSimple(krk_peek(1), 1, 1);
if (!IS_NOTIMPL(value)) goto _success;
krk_pop(); /* name */
} else {
krk_pop(); /* b */
krk_pop(); /* name */
}
return krk_runtimeError(vm.exceptions->typeError, msg, operator, krk_typeName(a), krk_typeName(b));
_success:
krk_pop(); /* name */
/* Return result */
return value;
}
/**
* Basic arithmetic and string functions follow.
*/
#define MAKE_BIN_OP(name,operator) \
#define MAKE_BIN_OP(name,operator,inv) \
KrkValue krk_operator_ ## name (KrkValue a, KrkValue b) { \
if (IS_INTEGER(a) && IS_INTEGER(b)) return INTEGER_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
if (IS_FLOATING(a)) { \
@ -1413,43 +1439,43 @@ static KrkValue tryBind(const char * name, KrkValue a, KrkValue b, const char *
} else if (IS_FLOATING(b)) { \
if (IS_INTEGER(a)) return FLOATING_VAL((double)AS_INTEGER(a) operator AS_FLOATING(b)); \
} \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'"); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'", "__" #inv "__"); \
}
MAKE_BIN_OP(add,+)
MAKE_BIN_OP(sub,-)
MAKE_BIN_OP(mul,*)
MAKE_BIN_OP(div,/)
MAKE_BIN_OP(add,+,radd)
MAKE_BIN_OP(sub,-,ssub)
MAKE_BIN_OP(mul,*,rmul)
MAKE_BIN_OP(div,/,rdiv)
#define MAKE_UNOPTIMIZED_BIN_OP(name,operator) \
#define MAKE_UNOPTIMIZED_BIN_OP(name,operator,inv) \
KrkValue krk_operator_ ## name (KrkValue a, KrkValue b) { \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'"); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'", "__" #inv "__"); \
}
MAKE_UNOPTIMIZED_BIN_OP(pow,**)
MAKE_UNOPTIMIZED_BIN_OP(pow,**,rpow)
/* Bit ops are invalid on doubles in C, so we can't use the same set of macros for them;
* they should be invalid in Kuroko as well. */
#define MAKE_BIT_OP_BOOL(name,operator) \
#define MAKE_BIT_OP_BOOL(name,operator,inv) \
KrkValue krk_operator_ ## name (KrkValue a, KrkValue b) { \
if (IS_BOOLEAN(a) && IS_BOOLEAN(b)) return BOOLEAN_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
if (IS_INTEGER(a) && IS_INTEGER(b)) return INTEGER_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'"); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'", "__" #inv "__"); \
}
#define MAKE_BIT_OP(name,operator) \
#define MAKE_BIT_OP(name,operator,inv) \
KrkValue krk_operator_ ## name (KrkValue a, KrkValue b) { \
if (IS_INTEGER(a) && IS_INTEGER(b)) return INTEGER_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'"); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'", "__" #inv "__"); \
}
MAKE_BIT_OP_BOOL(or,|)
MAKE_BIT_OP_BOOL(xor,^)
MAKE_BIT_OP_BOOL(and,&)
MAKE_BIT_OP(lshift,<<)
MAKE_BIT_OP(rshift,>>)
MAKE_BIT_OP(mod,%) /* not a bit op, but doesn't work on floating point */
MAKE_BIT_OP_BOOL(or,|,ror)
MAKE_BIT_OP_BOOL(xor,^,rxor)
MAKE_BIT_OP_BOOL(and,&,rand)
MAKE_BIT_OP(lshift,<<,rlshift)
MAKE_BIT_OP(rshift,>>,rrshift)
MAKE_BIT_OP(mod,%,rmod) /* not a bit op, but doesn't work on floating point */
#define MAKE_COMPARATOR(name, operator) \
#define MAKE_COMPARATOR(name, operator,inv) \
KrkValue krk_operator_ ## name (KrkValue a, KrkValue b) { \
if (IS_INTEGER(a) && IS_INTEGER(b)) return BOOLEAN_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
if (IS_FLOATING(a)) { \
@ -1458,11 +1484,13 @@ MAKE_BIT_OP(mod,%) /* not a bit op, but doesn't work on floating point */
} else if (IS_FLOATING(b)) { \
if (IS_INTEGER(a)) return BOOLEAN_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
} \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'"); \
return tryBind("__" #name "__", a, b, #operator, "unsupported operand types for %s: '%s' and '%s'", "__" #inv "__"); \
}
MAKE_COMPARATOR(lt, <)
MAKE_COMPARATOR(gt, >)
MAKE_COMPARATOR(lt, <, gt)
MAKE_COMPARATOR(gt, >, lt)
MAKE_COMPARATOR(le, <=, ge)
MAKE_COMPARATOR(ge, >=, le)
/**
* At the end of each instruction cycle, we check the exception flag to see
@ -2128,6 +2156,8 @@ _finishReturn: (void)0;
}
case OP_LESS: BINARY_OP(lt);
case OP_GREATER: BINARY_OP(gt);
case OP_LESS_EQUAL: BINARY_OP(le);
case OP_GREATER_EQUAL: BINARY_OP(ge);
case OP_ADD: BINARY_OP(add);
case OP_SUBTRACT: BINARY_OP(sub)
case OP_MULTIPLY: BINARY_OP(mul)

View File

@ -0,0 +1,41 @@
class Foo:
def __init__(self):
self.val = 42
def __lt__(self, o):
print('call lt',o)
if isinstance(o,(int,float)):
return self.val < o
return NotImplemented
def __le__(self, o):
print('call le',o)
if isinstance(o,(int,float)):
return self.val <= o
return NotImplemented
def __gt__(self, o):
print('call gt',o)
if isinstance(o,(int,float)):
return self.val > o
return NotImplemented
def __ge__(self, o):
print('call ge',o)
if isinstance(o,(int,float)):
return self.val >= o
return NotImplemented
print(Foo() < 43)
print(Foo() > 43)
print(Foo() > 41)
print(Foo() < 41)
print(Foo() >= 41)
print(Foo() >= 42)
print(Foo() <= 43)
print(Foo() <= 42)
print('---')
print(43 > Foo())
print(43 < Foo())
print(41 < Foo())
print(41 > Foo())
print(41 <= Foo())
print(42 <= Foo())
print(43 >= Foo())
print(42 >= Foo())

View File

@ -0,0 +1,33 @@
call lt 43
True
call gt 43
False
call gt 41
True
call lt 41
False
call ge 41
True
call ge 42
True
call le 43
True
call le 42
True
---
call lt 43
True
call gt 43
False
call gt 41
True
call lt 41
False
call ge 41
True
call ge 42
True
call le 43
True
call le 42
True

View File

@ -0,0 +1,51 @@
class RAdder:
def __radd__(self, o):
print("__radd__ called",o)
return f'{o} + RAdder()'
def __rmul__(self, o):
print("__rmul__ called", o)
return f'{o} * RAdder()'
def __rdiv__(self, o):
print("__rdiv__ called", o)
return f'{o} / RAdder()'
def __rpow__(self, o):
print("__rpow__ called", o)
return f'{o} ** RAdder()'
def __rrshift__(self, o):
print("__rrshift__ called", o)
return f'{o} >> RAdder()'
def __rlshift__(self, o):
print("__rlshift__ called", o)
return f'{o} << RAdder()'
def __ror__(self, o):
print("__ror__ called", o)
return f'{o} | RAdder()'
def __rand__(self, o):
print("__rand__ called", o)
return f'{o} & RAdder()'
def __rxor__(self, o):
print("__rxor__ called", o)
return f'{o} ^ RAdder()'
def __rmod__(self, o):
print("__rmod__ called", o)
return f'{o} % RAdder()'
print(42 + RAdder())
print(42 * RAdder())
#print(42 / RAdder())
print(42 ** RAdder())
print(42 >> RAdder())
print(42 << RAdder())
print(42 | RAdder())
print(42 & RAdder())
print(42 ^ RAdder())
print(42 % RAdder())

View File

@ -0,0 +1,18 @@
__radd__ called 42
42 + RAdder()
__rmul__ called 42
42 * RAdder()
__rpow__ called 42
42 ** RAdder()
__rrshift__ called 42
42 >> RAdder()
__rlshift__ called 42
42 << RAdder()
__ror__ called 42
42 | RAdder()
__rand__ called 42
42 & RAdder()
__rxor__ called 42
42 ^ RAdder()
__rmod__ called 42
42 % RAdder()