1 module wren.primitive; 2 import wren.value; 3 import wren.vm : WrenVM; 4 5 @nogc: 6 7 // Validates that [value] is an integer within `[0, count)`. Also allows 8 // negative indices which map backwards from the end. Returns the valid positive 9 // index value. If invalid, reports an error and returns `UINT32_MAX`. 10 uint validateIndexValue(WrenVM* vm, uint count, double value, 11 const(char)* argName) 12 { 13 if (!validateIntValue(vm, value, argName)) return uint.max; 14 15 // Negative indices count from the end. 16 if (value < 0) value = count + value; 17 18 // Check bounds. 19 if (value >= 0 && value < count) return cast(uint)value; 20 21 vm.fiber.error = wrenStringFormat(vm, "$ out of bounds.", argName); 22 23 return uint.max; 24 } 25 26 bool validateFn(WrenVM* vm, Value arg, const(char)* argName) 27 { 28 if (IS_CLOSURE(arg)) return true; 29 return RETURN_ERROR(vm, "$ must be a function.", argName); 30 } 31 32 bool validateNum(WrenVM* vm, Value arg, const(char)* argName) 33 { 34 if (IS_NUM(arg)) return true; 35 return RETURN_ERROR(vm, "$ must be a number.", argName); 36 } 37 38 bool validateIntValue(WrenVM* vm, double value, const(char)* argName) 39 { 40 import core.stdc.math : trunc; 41 if (trunc(value) == value) return true; 42 return RETURN_ERROR(vm, "$ must be a number.", argName); 43 } 44 45 bool validateInt(WrenVM* vm, Value arg, const(char)* argName) 46 { 47 // Make sure it's a number first. 48 if (!validateNum(vm, arg, argName)) return false; 49 return validateIntValue(vm, AS_NUM(arg), argName); 50 } 51 52 bool validateKey(WrenVM* vm, Value arg) 53 { 54 if (wrenMapIsValidKey(arg)) return true; 55 56 return RETURN_ERROR(vm, "Key must be a value type."); 57 } 58 59 60 uint validateIndex(WrenVM* vm, Value arg, uint count, 61 const(char)* argName) 62 { 63 if (!validateNum(vm, arg, argName)) return uint.max; 64 return validateIndexValue(vm, count, AS_NUM(arg), argName); 65 } 66 67 bool validateString(WrenVM* vm, Value arg, const(char)* argName) 68 { 69 if (IS_STRING(arg)) return true; 70 return RETURN_ERROR(vm, "$ must be a string.", argName); 71 } 72 73 uint calculateRange(WrenVM* vm, ObjRange* range, uint* length, 74 int* step) 75 { 76 *step = 0; 77 78 // Edge case: an empty range is allowed at the end of a sequence. This way, 79 // list[0..-1] and list[0...list.count] can be used to copy a list even when 80 // empty. 81 if (range.from == *length && 82 range.to == (range.isInclusive ? -1.0 : cast(double)*length)) 83 { 84 *length = 0; 85 return 0; 86 } 87 88 uint from = validateIndexValue(vm, *length, range.from, "Range start"); 89 if (from == uint.max) return uint.max; 90 91 // Bounds check the end manually to handle exclusive ranges. 92 double value = range.to; 93 if (!validateIntValue(vm, value, "Range end")) return uint.max; 94 95 // Negative indices count from the end. 96 if (value < 0) value = *length + value; 97 98 // Convert the exclusive range to an inclusive one. 99 if (!range.isInclusive) 100 { 101 // An exclusive range with the same start and end points is empty. 102 if (value == from) 103 { 104 *length = 0; 105 return from; 106 } 107 108 // Shift the endpoint to make it inclusive, handling both increasing and 109 // decreasing ranges. 110 value += value >= from ? -1 : 1; 111 } 112 113 // Check bounds. 114 if (value < 0 || value >= *length) 115 { 116 vm.fiber.error = CONST_STRING(vm, "Range end out of bounds."); 117 return uint.max; 118 } 119 120 uint to = cast(uint)value; 121 import wren.math : abs; 122 *length = abs(cast(int)(from - to)) + 1; 123 *step = from < to ? 1 : -1; 124 return from; 125 } 126 127 import wren.value : MethodType; 128 129 struct WrenPrimitive 130 { 131 import wren.value : MethodType; 132 string className; 133 string primitiveName; 134 MethodType methodType = MethodType.METHOD_PRIMITIVE; 135 bool registerToSuperClass = false; 136 } 137 138 template PRIMITIVE(alias name, 139 alias func, 140 MethodType methodType = MethodType.METHOD_PRIMITIVE, 141 bool registerToSuperClass = false) 142 { 143 import wren.value : ObjClass, wrenSymbolTableEnsure, wrenBindMethod, Method; 144 void PRIMITIVE(WrenVM* vm, ObjClass* cls) { 145 import core.stdc.string : strlen; 146 int symbol = wrenSymbolTableEnsure(vm, 147 &vm.methodNames, name, strlen(name)); 148 Method method; 149 method.type = methodType; 150 method.as.primitive = &func; 151 152 static if (registerToSuperClass) { 153 wrenBindMethod(vm, cls.obj.classObj, symbol, method); 154 } else { 155 wrenBindMethod(vm, cls, symbol, method); 156 } 157 } 158 } 159 160 bool RETURN_VAL(Value* args, Value v) 161 { 162 args[0] = v; 163 return true; 164 } 165 166 bool RETURN_OBJ(T)(Value* args, T* obj) 167 { 168 return RETURN_VAL(args, OBJ_VAL(obj)); 169 } 170 171 bool RETURN_BOOL(Value* args, bool val) 172 { 173 return RETURN_VAL(args, BOOL_VAL(val)); 174 } 175 176 bool RETURN_FALSE(Value* args) 177 { 178 return RETURN_VAL(args, FALSE_VAL); 179 } 180 181 bool RETURN_NULL(Value* args) 182 { 183 return RETURN_VAL(args, NULL_VAL); 184 } 185 186 bool RETURN_NUM(N)(Value* args, N val) 187 { 188 return RETURN_VAL(args, NUM_VAL(val)); 189 } 190 191 bool RETURN_TRUE(Value* args) 192 { 193 return RETURN_VAL(args, TRUE_VAL); 194 } 195 196 bool RETURN_ERROR(WrenVM* vm, const(char)* msg) 197 { 198 import core.stdc.string : strlen; 199 vm.fiber.error = wrenNewStringLength(vm, msg, strlen(msg)); 200 return false; 201 } 202 203 bool RETURN_ERROR(WrenVM* vm, const(char)* fmt, ...) 204 { 205 import core.stdc.stdarg; 206 va_list args; 207 va_start!(const(char)*)(args, fmt); 208 vm.fiber.error = wrenStringFormat(vm, fmt, args); 209 va_end(args); 210 return false; 211 }