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 }