1 /*
2 * OpenSCAD (www.openscad.org)
3 * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4 * Marius Kintel <marius@kintel.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * As a special exception, you have permission to link this program
12 * with the CGAL library and distribute executables, as long as you
13 * follow the requirements of the GNU GPL in regard to all of the
14 * software in the executable aside from CGAL.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27 #include "function.h"
28 #include "expression.h"
29 #include "evalcontext.h"
30 #include "builtin.h"
31 #include "printutils.h"
32 #include "stackcheck.h"
33 #include "exceptions.h"
34 #include "memory.h"
35 #include "UserModule.h"
36 #include "degree_trig.h"
37
38 #include <cmath>
39 #include <sstream>
40 #include <ctime>
41 #include <limits>
42 #include <algorithm>
43 #include <random>
44
45 #include"boost-utils.h"
46 /*Unicode support for string lengths and array accesses*/
47 #include <glib.h>
48 // hash double
49 #include "linalg.h"
50
51 #if defined __WIN32__ || defined _MSC_VER
52 #include <process.h>
53 int process_id = _getpid();
54 #else
55 #include <sys/types.h>
56 #include <unistd.h>
57 int process_id = getpid();
58 #endif
59
60 std::mt19937 deterministic_rng( std::time(nullptr) + process_id );
61
print_argCnt_warning(const char * name,const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)62 static void print_argCnt_warning(const char *name, const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx){
63 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"%1$s() number of parameters does not match",name);
64 }
65
print_argConvert_warning(const char * name,const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)66 static void print_argConvert_warning(const char *name, const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx){
67 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"%1$s() parameter could not be converted",name);
68 }
69
builtin_abs(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)70 Value builtin_abs(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
71 {
72 if (evalctx->numArgs() == 1) {
73 Value v = evalctx->getArgValue(0);
74 if (v.type() == Value::Type::NUMBER){
75 return Value(std::fabs(v.toDouble()));
76 } else {
77 print_argConvert_warning("abs", ctx, evalctx);
78 }
79 } else {
80 print_argCnt_warning("abs", ctx, evalctx);
81 }
82
83 return Value::undefined.clone();
84 }
85
builtin_sign(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)86 Value builtin_sign(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
87 {
88 if (evalctx->numArgs() == 1) {
89 Value v = evalctx->getArgValue(0);
90 if (v.type() == Value::Type::NUMBER) {
91 double x = v.toDouble();
92 return Value((x<0) ? -1.0 : ((x>0) ? 1.0 : 0.0));
93 } else {
94 print_argConvert_warning("sign", ctx, evalctx);
95 }
96 } else {
97 print_argCnt_warning("sign", ctx, evalctx);
98 }
99 return Value::undefined.clone();
100 }
101
builtin_rands(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)102 Value builtin_rands(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
103 {
104 size_t n = evalctx->numArgs();
105 if (n == 3 || n == 4) {
106 Value v0 = evalctx->getArgValue(0);
107 if (v0.type() != Value::Type::NUMBER) goto quit;
108 double min = v0.toDouble();
109
110 if (std::isinf(min) || std::isnan(min)){
111 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"rands() range min cannot be infinite");
112 min = -std::numeric_limits<double>::max()/2;
113 LOG(message_group::Warning,Location::NONE,"","resetting to %1f",min);
114 }
115 Value v1 = evalctx->getArgValue(1);
116 if (v1.type() != Value::Type::NUMBER) goto quit;
117 double max = v1.toDouble();
118 if (std::isinf(max) || std::isnan(max)) {
119 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"rands() range max cannot be infinite");
120 max = std::numeric_limits<double>::max()/2;
121 LOG(message_group::Warning,Location::NONE,"","resetting to %1f",max);
122 }
123 if (max < min) {
124 double tmp = min; min = max; max = tmp;
125 }
126 Value v2 = evalctx->getArgValue(2);
127 if (v2.type() != Value::Type::NUMBER) goto quit;
128 double numresultsd = std::abs( v2.toDouble() );
129 if (std::isinf(numresultsd) || std::isnan(numresultsd)) {
130 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"rands() cannot create an infinite number of results");
131 LOG(message_group::Warning,Location::NONE,"","resetting number of results to 1");
132 numresultsd = 1;
133 }
134 size_t numresults = boost_numeric_cast<size_t,double>( numresultsd );
135
136 if (n > 3) {
137 Value v3 = evalctx->getArgValue(3);
138 if (v3.type() != Value::Type::NUMBER) goto quit;
139 uint32_t seed = static_cast<uint32_t>(hash_floating_point( v3.toDouble() ));
140 deterministic_rng.seed( seed );
141 }
142 VectorType vec;
143 if (min>=max) { // uniform_real_distribution doesn't allow min == max
144 for (size_t i=0; i < numresults; ++i)
145 vec.emplace_back(min);
146 } else {
147 std::uniform_real_distribution<> distributor( min, max );
148 for (size_t i=0; i < numresults; ++i) {
149 vec.emplace_back(distributor(deterministic_rng));
150 }
151 }
152 return std::move(vec);
153 } else {
154 print_argCnt_warning("rands", ctx, evalctx);
155 }
156 quit:
157 return Value::undefined.clone();
158 }
159
builtin_min(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)160 Value builtin_min(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
161 {
162 // preserve special handling of the first argument
163 // as a template for vector processing
164 size_t n = evalctx->numArgs();
165 if (n >= 1) {
166 Value v0 = evalctx->getArgValue(0);
167
168 if (n == 1 && v0.type() == Value::Type::VECTOR) {
169 const auto &vec = v0.toVector();
170 if (!vec.empty()) {
171 return std::min_element(vec.begin(), vec.end(), Value::cmp_less)->clone();
172 }
173 }
174 if (v0.type() == Value::Type::NUMBER) {
175 double val = v0.toDouble();
176 for (size_t i = 1; i < n; ++i) {
177 Value v = evalctx->getArgValue(i);
178 // 4/20/14 semantic change per discussion:
179 // break on any non-number
180 if (v.type() != Value::Type::NUMBER) goto quit;
181 double x = v.toDouble();
182 if (x < val) val = x;
183 }
184 return Value(val);
185 }
186 } else {
187 print_argCnt_warning("min", ctx, evalctx);
188 return Value::undefined.clone();
189 }
190 quit:
191 print_argConvert_warning("min", ctx, evalctx);
192 return Value::undefined.clone();
193 }
194
builtin_max(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)195 Value builtin_max(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
196 {
197 // preserve special handling of the first argument
198 // as a template for vector processing
199 size_t n = evalctx->numArgs();
200 if (n >= 1) {
201 Value v0 = evalctx->getArgValue(0);
202
203 if (n == 1 && v0.type() == Value::Type::VECTOR) {
204 const auto &vec = v0.toVector();
205 if (!vec.empty()) {
206 return std::max_element(vec.begin(), vec.end(), Value::cmp_less)->clone();
207 }
208 }
209 if (v0.type() == Value::Type::NUMBER) {
210 double val = v0.toDouble();
211 for (size_t i = 1; i < n; ++i) {
212 Value v = evalctx->getArgValue(i);
213 // 4/20/14 semantic change per discussion:
214 // break on any non-number
215 if (v.type() != Value::Type::NUMBER) goto quit;
216 double x = v.toDouble();
217 if (x > val) val = x;
218 }
219 return Value(val);
220 }
221 } else {
222 print_argCnt_warning("max", ctx, evalctx);
223 return Value::undefined.clone();
224 }
225 quit:
226 print_argConvert_warning("max", ctx, evalctx);
227 return Value::undefined.clone();
228 }
229
builtin_sin(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)230 Value builtin_sin(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
231 {
232 if (evalctx->numArgs() == 1) {
233 Value v = evalctx->getArgValue(0);
234 if (v.type() == Value::Type::NUMBER){
235 return Value(sin_degrees(v.toDouble()));
236 } else {
237 print_argConvert_warning("sin", ctx, evalctx);
238 }
239 } else {
240 print_argCnt_warning("sin", ctx, evalctx);
241 }
242 return Value::undefined.clone();
243 }
244
builtin_cos(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)245 Value builtin_cos(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
246 {
247 if (evalctx->numArgs() == 1) {
248 Value v = evalctx->getArgValue(0);
249 if (v.type() == Value::Type::NUMBER){
250 return Value(cos_degrees(v.toDouble()));
251 } else {
252 print_argConvert_warning("cos", ctx, evalctx);
253 }
254 } else {
255 print_argCnt_warning("cos", ctx, evalctx);
256 }
257 return Value::undefined.clone();
258 }
259
builtin_asin(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)260 Value builtin_asin(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
261 {
262 if (evalctx->numArgs() == 1) {
263 Value v = evalctx->getArgValue(0);
264 if (v.type() == Value::Type::NUMBER){
265 return Value(asin_degrees(v.toDouble()));
266 } else {
267 print_argConvert_warning("asin", ctx, evalctx);
268 }
269 } else {
270 print_argCnt_warning("asin", ctx, evalctx);
271 }
272 return Value::undefined.clone();
273 }
274
builtin_acos(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)275 Value builtin_acos(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
276 {
277 if (evalctx->numArgs() == 1) {
278 Value v = evalctx->getArgValue(0);
279 if (v.type() == Value::Type::NUMBER){
280 return Value(acos_degrees(v.toDouble()));
281 } else {
282 print_argConvert_warning("acos", ctx, evalctx);
283 }
284 } else {
285 print_argCnt_warning("acos", ctx, evalctx);
286 }
287 return Value::undefined.clone();
288 }
289
builtin_tan(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)290 Value builtin_tan(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
291 {
292 if (evalctx->numArgs() == 1) {
293 Value v = evalctx->getArgValue(0);
294 if (v.type() == Value::Type::NUMBER){
295 return Value(tan_degrees(v.toDouble()));
296 } else {
297 print_argConvert_warning("tan", ctx, evalctx);
298 }
299 } else {
300 print_argCnt_warning("tan", ctx, evalctx);
301 }
302 return Value::undefined.clone();
303 }
304
builtin_atan(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)305 Value builtin_atan(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
306 {
307 if (evalctx->numArgs() == 1) {
308 Value v = evalctx->getArgValue(0);
309 if (v.type() == Value::Type::NUMBER){
310 return Value(atan_degrees(v.toDouble()));
311 } else {
312 print_argConvert_warning("atan", ctx, evalctx);
313 }
314 } else {
315 print_argCnt_warning("atan", ctx, evalctx);
316 }
317 return Value::undefined.clone();
318 }
319
builtin_atan2(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)320 Value builtin_atan2(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
321 {
322 if (evalctx->numArgs() == 2) {
323 Value v0 = evalctx->getArgValue(0), v1 = evalctx->getArgValue(1);
324 if (v0.type() == Value::Type::NUMBER && v1.type() == Value::Type::NUMBER){
325 return Value(atan2_degrees(v0.toDouble(), v1.toDouble()));
326 } else {
327 print_argConvert_warning("atan2", ctx, evalctx);
328 }
329 } else {
330 print_argCnt_warning("atan2", ctx, evalctx);
331 }
332 return Value::undefined.clone();
333 }
334
builtin_pow(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)335 Value builtin_pow(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
336 {
337 if (evalctx->numArgs() == 2) {
338 Value v0 = evalctx->getArgValue(0), v1 = evalctx->getArgValue(1);
339 if (v0.type() == Value::Type::NUMBER && v1.type() == Value::Type::NUMBER){
340 return Value(pow(v0.toDouble(), v1.toDouble()));
341 } else {
342 print_argConvert_warning("pow", ctx, evalctx);
343 }
344 } else {
345 print_argCnt_warning("pow", ctx, evalctx);
346 }
347 return Value::undefined.clone();
348 }
349
builtin_round(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)350 Value builtin_round(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
351 {
352 if (evalctx->numArgs() == 1) {
353 Value v = evalctx->getArgValue(0);
354 if (v.type() == Value::Type::NUMBER){
355 return Value(round(v.toDouble()));
356 } else {
357 print_argConvert_warning("round", ctx, evalctx);
358 }
359 } else {
360 print_argCnt_warning("round", ctx, evalctx);
361 }
362 return Value::undefined.clone();
363 }
364
builtin_ceil(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)365 Value builtin_ceil(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
366 {
367 if (evalctx->numArgs() == 1) {
368 Value v = evalctx->getArgValue(0);
369 if (v.type() == Value::Type::NUMBER){
370 return Value(ceil(v.toDouble()));
371 } else {
372 print_argConvert_warning("ceil", ctx, evalctx);
373 }
374 } else {
375 print_argCnt_warning("ceil", ctx, evalctx);
376 }
377 return Value::undefined.clone();
378 }
379
builtin_floor(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)380 Value builtin_floor(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
381 {
382 if (evalctx->numArgs() == 1) {
383 Value v = evalctx->getArgValue(0);
384 if (v.type() == Value::Type::NUMBER){
385 return Value(floor(v.toDouble()));
386 } else {
387 print_argConvert_warning("floor", ctx, evalctx);
388 }
389 } else {
390 print_argCnt_warning("floor", ctx, evalctx);
391 }
392 return Value::undefined.clone();
393 }
394
builtin_sqrt(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)395 Value builtin_sqrt(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
396 {
397 if (evalctx->numArgs() == 1) {
398 Value v = evalctx->getArgValue(0);
399 if (v.type() == Value::Type::NUMBER){
400 return Value(sqrt(v.toDouble()));
401 } else {
402 print_argConvert_warning("sqrt", ctx, evalctx);
403 }
404 } else {
405 print_argCnt_warning("sqrt", ctx, evalctx);
406 }
407 return Value::undefined.clone();
408 }
409
builtin_exp(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)410 Value builtin_exp(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
411 {
412 if (evalctx->numArgs() == 1) {
413 Value v = evalctx->getArgValue(0);
414 if (v.type() == Value::Type::NUMBER){
415 return Value(exp(v.toDouble()));
416 } else {
417 print_argConvert_warning("exp", ctx, evalctx);
418 }
419 } else {
420 print_argCnt_warning("exp", ctx, evalctx);
421 }
422 return Value::undefined.clone();
423 }
424
builtin_length(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)425 Value builtin_length(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
426 {
427 if (evalctx->numArgs() == 1) {
428 Value v = evalctx->getArgValue(0);
429 if (v.type() == Value::Type::VECTOR) return double(v.toVector().size());
430 if (v.type() == Value::Type::STRING) {
431 //Unicode glyph count for the length -- rather than the string (num. of bytes) length.
432 return Value(double( v.toStrUtf8Wrapper().get_utf8_strlen()));
433 }
434 print_argConvert_warning("len", ctx, evalctx);
435 } else {
436 print_argCnt_warning("len", ctx, evalctx);
437 }
438 return Value::undefined.clone();
439 }
440
builtin_log(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)441 Value builtin_log(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
442 {
443 size_t n = evalctx->numArgs();
444 if (n == 1 || n == 2) {
445 Value v0 = evalctx->getArgValue(0);
446 if (v0.type() == Value::Type::NUMBER) {
447 double x = 10.0, y = v0.toDouble();
448 if (n > 1) {
449 Value v1 = evalctx->getArgValue(1);
450 if (v1.type() != Value::Type::NUMBER) goto quit;
451 x = y; y = v1.toDouble();
452 }
453 return Value(log(y) / log(x));
454 }
455 } else {
456 print_argCnt_warning("log", ctx, evalctx);
457 return Value::undefined.clone();
458 }
459 quit:
460 print_argConvert_warning("log", ctx, evalctx);
461 return Value::undefined.clone();
462 }
463
builtin_ln(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)464 Value builtin_ln(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
465 {
466 if (evalctx->numArgs() == 1) {
467 Value v = evalctx->getArgValue(0);
468 if (v.type() == Value::Type::NUMBER){
469 return Value(log(v.toDouble()));
470 } else {
471 print_argConvert_warning("ln", ctx, evalctx);
472 }
473 } else {
474 print_argCnt_warning("ln", ctx, evalctx);
475 }
476 return Value::undefined.clone();
477 }
478
builtin_str(const std::shared_ptr<Context>,const std::shared_ptr<EvalContext> evalctx)479 Value builtin_str(const std::shared_ptr<Context>, const std::shared_ptr<EvalContext> evalctx)
480 {
481 std::ostringstream stream;
482
483 for (size_t i = 0; i < evalctx->numArgs(); ++i) {
484 stream << evalctx->getArgValue(i).toString();
485 }
486 return Value(stream.str());
487 }
488
builtin_chr(const std::shared_ptr<Context>,const std::shared_ptr<EvalContext> evalctx)489 Value builtin_chr(const std::shared_ptr<Context>, const std::shared_ptr<EvalContext> evalctx)
490 {
491 std::ostringstream stream;
492
493 for (size_t i = 0; i < evalctx->numArgs(); ++i) {
494 Value v = evalctx->getArgValue(i);
495 stream << v.chrString();
496 }
497 return Value(stream.str());
498 }
499
builtin_ord(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)500 Value builtin_ord(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
501 {
502 const size_t numArgs = evalctx->numArgs();
503
504 if (numArgs == 0) {
505 return Value::undefined.clone();
506 } else if (numArgs > 1) {
507 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"ord() called with %1$d arguments, only 1 argument expected",numArgs);
508 return Value::undefined.clone();
509 }
510
511 const Value arg = evalctx->getArgValue(0);
512 if (arg.type() != Value::Type::STRING) {
513 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"ord() argument %1$s is not of type string",arg.toEchoString());
514 return Value::undefined.clone();
515 }
516
517 const str_utf8_wrapper &arg_str = arg.toStrUtf8Wrapper();
518 const char *ptr = arg_str.c_str();
519 if (!g_utf8_validate(ptr, -1, NULL)) {
520 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"ord() argument '%1$s' is not a valid utf8 string",arg_str.toString());
521 return Value::undefined.clone();
522 }
523
524 if (arg_str.get_utf8_strlen() == 0) {
525 return Value::undefined.clone();
526 }
527
528 const gunichar ch = g_utf8_get_char(ptr);
529 return Value((double)ch);
530 }
531
builtin_concat(const std::shared_ptr<Context>,const std::shared_ptr<EvalContext> evalctx)532 Value builtin_concat(const std::shared_ptr<Context>, const std::shared_ptr<EvalContext> evalctx)
533 {
534 VectorType result;
535
536 for (size_t i = 0; i < evalctx->numArgs(); ++i) {
537 Value val = evalctx->getArgValue(i);
538 if (val.type() == Value::Type::VECTOR) {
539 result.emplace_back(EmbeddedVectorType(std::move(val.toVectorNonConst())));
540 } else {
541 result.emplace_back(std::move(val));
542 }
543 }
544 return std::move(result);
545 }
546
builtin_lookup(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)547 Value builtin_lookup(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
548 {
549 double p, low_p, low_v, high_p, high_v;
550 if (evalctx->numArgs() != 2){ // Needs two args
551 print_argCnt_warning("lookup", ctx, evalctx);
552 return Value::undefined.clone();
553 }
554 if(!evalctx->getArgValue(0).getDouble(p) || !std::isfinite(p)){ // First arg must be a number
555 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"lookup(%1$s, ...) first argument is not a number",evalctx->getArgValue(0).toEchoString());
556 return Value::undefined.clone();
557 }
558
559 Value v1 = evalctx->getArgValue(1);
560 const auto &vec = v1.toVector();
561
562 // Second must be a vector of vec2, with valid numbers inside
563 auto it = vec.begin();
564 if (vec.empty() || it->toVector().size() < 2 || !it->getVec2(low_p, low_v)) {
565 return Value::undefined.clone();
566 }
567 high_p = low_p;
568 high_v = low_v;
569
570 for (++it; it != vec.end(); ++it) {
571 double this_p, this_v;
572 if (it->getVec2(this_p, this_v)) {
573 if (this_p <= p && (this_p > low_p || low_p > p)) {
574 low_p = this_p;
575 low_v = this_v;
576 }
577 if (this_p >= p && (this_p < high_p || high_p < p)) {
578 high_p = this_p;
579 high_v = this_v;
580 }
581 }
582 }
583 if (p <= low_p)
584 return Value(high_v);
585 if (p >= high_p)
586 return Value(low_v);
587 double f = (p-low_p) / (high_p-low_p);
588 return Value(high_v * f + low_v * (1-f));
589 }
590
591 /*
592 Pattern:
593
594 "search" "(" ( match_value | list_of_match_values ) "," vector_of_vectors
595 ("," num_returns_per_match
596 ("," index_col_num )? )?
597 ")";
598 match_value : ( Value::Type::NUMBER | Value::Type::STRING );
599 list_of_values : "[" match_value ("," match_value)* "]";
600 vector_of_vectors : "[" ("[" Value ("," Value)* "]")+ "]";
601 num_returns_per_match : int;
602 index_col_num : int;
603
604 The search string and searched strings can be unicode strings.
605 Examples:
606 Index values return as list:
607 search("a","abcdabcd");
608 - returns [0]
609 search("Л","Л"); //A unicode string
610 - returns [0]
611 search("aЛ","aЛaЛa",0);
612 - returns [[1,3,5,7],[0,4,8],[2,6]]
613 search("a","abcdabcd",0); //Search up to all matches
614 - returns [[0,4]]
615 search("a","abcdabcd",1);
616 - returns [0]
617 search("e","abcdabcd",1);
618 - returns []
619 search("a",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ]);
620 - returns [0,4]
621
622 Search on different column; return Index values:
623 search(3,[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",3] ], 0, 1);
624 - returns [0,8]
625
626 Search on list of values:
627 Return all matches per search vector element:
628 search("abc",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ], 0);
629 - returns [[0,4],[1,5],[2,6]]
630
631 Return first match per search vector element; special case return vector:
632 search("abc",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ], 1);
633 - returns [0,1,2]
634
635 Return first two matches per search vector element; vector of vectors:
636 search("abce",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ], 2);
637 - returns [[0,4],[1,5],[2,6],[8]]
638
639 */
640
search(const str_utf8_wrapper & find,const str_utf8_wrapper & table,unsigned int num_returns_per_match,const Location &)641 static VectorType search(const str_utf8_wrapper &find, const str_utf8_wrapper &table,
642 unsigned int num_returns_per_match,
643 const Location &)
644 {
645 VectorType returnvec;
646 //Unicode glyph count for the length
647 size_t findThisSize = find.get_utf8_strlen();
648 size_t searchTableSize = table.get_utf8_strlen();
649 for (size_t i = 0; i < findThisSize; ++i) {
650 unsigned int matchCount = 0;
651 VectorType resultvec;
652 const gchar *ptr_ft = g_utf8_offset_to_pointer(find.c_str(), i);
653 for (size_t j = 0; j < searchTableSize; ++j) {
654 const gchar *ptr_st = g_utf8_offset_to_pointer(table.c_str(), j);
655 if (ptr_ft && ptr_st && (g_utf8_get_char(ptr_ft) == g_utf8_get_char(ptr_st)) ) {
656 matchCount++;
657 if (num_returns_per_match == 1) {
658 returnvec.emplace_back(double(j));
659 break;
660 } else {
661 resultvec.emplace_back(double(j));
662 }
663 if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) {
664 break;
665 }
666 }
667 }
668 if (matchCount == 0) {
669 gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into
670 if (ptr_ft) g_utf8_strncpy(utf8_of_cp, ptr_ft, 1);
671 }
672 if (num_returns_per_match == 0 || num_returns_per_match > 1) {
673 returnvec.emplace_back(std::move(resultvec));
674 }
675 }
676 return returnvec;
677 }
678
search(const str_utf8_wrapper & find,const VectorType & table,unsigned int num_returns_per_match,unsigned int index_col_num,const Location & loc,const std::shared_ptr<Context> ctx)679 static VectorType search(const str_utf8_wrapper &find, const VectorType &table,
680 unsigned int num_returns_per_match, unsigned int index_col_num,
681 const Location &loc, const std::shared_ptr<Context> ctx)
682 {
683 VectorType returnvec;
684 //Unicode glyph count for the length
685 unsigned int findThisSize = find.get_utf8_strlen();
686 unsigned int searchTableSize = table.size();
687 for (size_t i = 0; i < findThisSize; ++i) {
688 unsigned int matchCount = 0;
689 VectorType resultvec;
690 const gchar *ptr_ft = g_utf8_offset_to_pointer(find.c_str(), i);
691 for (size_t j = 0; j < searchTableSize; ++j) {
692 const auto &entryVec = table[j].toVector();
693 if (entryVec.size() <= index_col_num) {
694 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid entry in search vector at index %1$d, required number of values in the entry: %2$d. Invalid entry: %3$s",j,(index_col_num + 1),table[j].toEchoString());
695 return VectorType();
696 }
697 const gchar *ptr_st = g_utf8_offset_to_pointer(entryVec[index_col_num].toString().c_str(), 0);
698 if (ptr_ft && ptr_st && (g_utf8_get_char(ptr_ft) == g_utf8_get_char(ptr_st)) ) {
699 matchCount++;
700 if (num_returns_per_match == 1) {
701 returnvec.emplace_back(double(j));
702 break;
703 } else {
704 resultvec.emplace_back(double(j));
705 }
706 if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) {
707 break;
708 }
709 }
710 }
711 if (matchCount == 0) {
712 gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into
713 if (ptr_ft) g_utf8_strncpy(utf8_of_cp, ptr_ft, 1);
714 LOG(message_group::Warning,loc,ctx->documentPath(),"search term not found: \"%1$s\"",utf8_of_cp);
715 }
716 if (num_returns_per_match == 0 || num_returns_per_match > 1) {
717 returnvec.emplace_back(std::move(resultvec));
718 }
719 }
720 return returnvec;
721 }
722
builtin_search(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)723 Value builtin_search(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
724 {
725 if (evalctx->numArgs() < 2){
726 print_argCnt_warning("search", ctx, evalctx);
727 return Value::undefined.clone();
728 }
729
730 Value findThis = evalctx->getArgValue(0);
731 Value searchTable = evalctx->getArgValue(1);
732 unsigned int num_returns_per_match = (evalctx->numArgs() > 2) ? (unsigned int)evalctx->getArgValue(2).toDouble() : 1;
733 unsigned int index_col_num = (evalctx->numArgs() > 3) ? (unsigned int)evalctx->getArgValue(3).toDouble() : 0;
734
735 VectorType returnvec;
736
737 if (findThis.type() == Value::Type::NUMBER) {
738 unsigned int matchCount = 0;
739 size_t j = 0;
740 for (const auto &search_element : searchTable.toVector()) {
741 if ((index_col_num == 0 && (findThis == search_element).toBool()) ||
742 (index_col_num < search_element.toVector().size() &&
743 (findThis == search_element.toVector()[index_col_num]).toBool())) {
744 returnvec.emplace_back(double(j));
745 matchCount++;
746 if (num_returns_per_match != 0 && matchCount >= num_returns_per_match) break;
747 }
748 ++j;
749 }
750 } else if (findThis.type() == Value::Type::STRING) {
751 if (searchTable.type() == Value::Type::STRING) {
752 returnvec = search(findThis.toStrUtf8Wrapper(), searchTable.toStrUtf8Wrapper(), num_returns_per_match, evalctx->loc);
753 }
754 else {
755 returnvec = search(findThis.toStrUtf8Wrapper(), searchTable.toVector(), num_returns_per_match, index_col_num, evalctx->loc, ctx);
756 }
757 } else if (findThis.type() == Value::Type::VECTOR) {
758 const auto &findVec = findThis.toVector();
759 for (size_t i = 0; i < findVec.size(); ++i) {
760 unsigned int matchCount = 0;
761 VectorType resultvec;
762
763 const auto &find_value = findVec[i];
764 size_t j = 0;
765 for (const auto &search_element : searchTable.toVector()) {
766 if ((index_col_num == 0 && (find_value == search_element).toBool()) ||
767 (index_col_num < search_element.toVector().size() &&
768 (find_value == search_element.toVector()[index_col_num]).toBool())) {
769 matchCount++;
770 if (num_returns_per_match == 1) {
771 returnvec.emplace_back(double(j));
772 break;
773 } else {
774 resultvec.emplace_back(double(j));
775 }
776 if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) break;
777 }
778 ++j;
779 }
780 if (num_returns_per_match == 1 && matchCount == 0) {
781 returnvec.emplace_back(std::move(resultvec));
782 }
783 if (num_returns_per_match == 0 || num_returns_per_match > 1) {
784 returnvec.emplace_back(std::move(resultvec));
785 }
786 }
787 } else {
788 return Value::undefined.clone();
789 }
790 return std::move(returnvec);
791 }
792
793 #define QUOTE(x__) # x__
794 #define QUOTED(x__) QUOTE(x__)
795
builtin_version(const std::shared_ptr<Context>,const std::shared_ptr<EvalContext>)796 Value builtin_version(const std::shared_ptr<Context>, const std::shared_ptr<EvalContext>)
797 {
798 VectorType vec;
799 vec.emplace_back(double(OPENSCAD_YEAR));
800 vec.emplace_back(double(OPENSCAD_MONTH));
801 #ifdef OPENSCAD_DAY
802 vec.emplace_back(double(OPENSCAD_DAY));
803 #endif
804 return std::move(vec);
805 }
806
builtin_version_num(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)807 Value builtin_version_num(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
808 {
809 Value val = (evalctx->numArgs() == 0) ? builtin_version(ctx, evalctx) : evalctx->getArgValue(0);
810 double y, m, d;
811 if (!val.getVec3(y, m, d, 0)) {
812 return Value::undefined.clone();
813 }
814 return Value(y * 10000 + m * 100 + d);
815 }
816
builtin_parent_module(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)817 Value builtin_parent_module(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
818 {
819 int n;
820 double d;
821 int s = UserModule::stack_size();
822 if (evalctx->numArgs() == 0)
823 d=1; // parent module
824 else if (evalctx->numArgs() == 1) {
825 Value v = evalctx->getArgValue(0);
826 if (v.type() != Value::Type::NUMBER) return Value::undefined.clone();
827 v.getDouble(d);
828 } else {
829 print_argCnt_warning("parent_module", ctx, evalctx);
830 return Value::undefined.clone();
831 }
832 n=trunc(d);
833 if (n < 0) {
834 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"Negative parent module index (%1$d) not allowed",n);
835 return Value::undefined.clone();
836 }
837 if (n >= s) {
838 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"Parent module index (%1$d) greater than the number of modules on the stack",n);
839 return Value::undefined.clone();
840 }
841 return Value(UserModule::stack_element(s - 1 - n));
842 }
843
builtin_norm(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)844 Value builtin_norm(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
845 {
846 if (evalctx->numArgs() == 1) {
847 Value val = evalctx->getArgValue(0);
848 if (val.type() == Value::Type::VECTOR) {
849 double sum = 0;
850 for (const auto &v : val.toVector()) {
851 if (v.type() == Value::Type::NUMBER) {
852 // sum += pow(v[i].toDouble(),2);
853 double x = v.toDouble();
854 sum += x*x;
855 } else {
856 LOG(message_group::Warning,evalctx->loc,ctx->documentPath(),"Incorrect arguments to norm()");
857 return Value::undefined.clone();
858 }
859 }
860 return Value(sqrt(sum));
861 }
862 } else {
863 print_argCnt_warning("norm", ctx, evalctx);
864 }
865 return Value::undefined.clone();
866 }
867
builtin_cross(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)868 Value builtin_cross(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
869 {
870 auto loc = evalctx->loc;
871 if (evalctx->numArgs() != 2) {
872 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid number of parameters for cross()");
873 return Value::undefined.clone();
874 }
875
876 Value arg0 = evalctx->getArgValue(0);
877 Value arg1 = evalctx->getArgValue(1);
878 if ((arg0.type() != Value::Type::VECTOR) || (arg1.type() != Value::Type::VECTOR)) {
879 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid type of parameters for cross()");
880 return Value::undefined.clone();
881 }
882
883 const auto &v0 = arg0.toVector();
884 const auto &v1 = arg1.toVector();
885 if ((v0.size() == 2) && (v1.size() == 2)) {
886 return Value(v0[0].toDouble() * v1[1].toDouble() - v0[1].toDouble() * v1[0].toDouble());
887 }
888
889 if ((v0.size() != 3) || (v1.size() != 3)) {
890 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid vector size of parameter for cross()");
891 return Value::undefined.clone();
892 }
893 for (unsigned int a = 0;a < 3; ++a) {
894 if ((v0[a].type() != Value::Type::NUMBER) || (v1[a].type() != Value::Type::NUMBER)) {
895 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid value in parameter vector for cross()");
896 return Value::undefined.clone();
897 }
898 double d0 = v0[a].toDouble();
899 double d1 = v1[a].toDouble();
900 if (std::isnan(d0) || std::isnan(d1)) {
901 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid value (NaN) in parameter vector for cross()");
902 return Value::undefined.clone();
903 }
904 if (std::isinf(d0) || std::isinf(d1)) {
905 LOG(message_group::Warning,loc,ctx->documentPath(),"Invalid value (INF) in parameter vector for cross()");
906 return Value::undefined.clone();
907 }
908 }
909
910 double x = v0[1].toDouble() * v1[2].toDouble() - v0[2].toDouble() * v1[1].toDouble();
911 double y = v0[2].toDouble() * v1[0].toDouble() - v0[0].toDouble() * v1[2].toDouble();
912 double z = v0[0].toDouble() * v1[1].toDouble() - v0[1].toDouble() * v1[0].toDouble();
913
914 return VectorType(x,y,z);
915 }
916
builtin_is_undef(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)917 Value builtin_is_undef(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
918 {
919 if (evalctx->numArgs() == 1) {
920 const auto &arg =evalctx->getArgs()[0];
921 if (auto lookup = dynamic_pointer_cast<Lookup>(arg->getExpr())) {
922 return lookup->evaluateSilently(evalctx).isUndefined();
923 } else {
924 return evalctx->getArgValue(0).isUndefined();
925 }
926 } else {
927 print_argCnt_warning("is_undef", ctx, evalctx);
928 }
929 return Value::undefined.clone();
930 }
931
builtin_is_list(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)932 Value builtin_is_list(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
933 {
934 if (evalctx->numArgs() == 1) {
935 return Value(evalctx->getArgValue(0).isDefinedAs(Value::Type::VECTOR));
936 } else {
937 print_argCnt_warning("is_list", ctx, evalctx);
938 }
939 return Value::undefined.clone();
940 }
941
builtin_is_num(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)942 Value builtin_is_num(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
943 {
944 if (evalctx->numArgs() == 1) {
945 Value v = evalctx->getArgValue(0);
946 return Value(v.isDefinedAs(Value::Type::NUMBER) && !std::isnan(v.toDouble()));
947 } else {
948 print_argCnt_warning("is_num", ctx, evalctx);
949 }
950 return Value::undefined.clone();
951 }
952
builtin_is_bool(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)953 Value builtin_is_bool(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
954 {
955 if (evalctx->numArgs() == 1) {
956 return Value(evalctx->getArgValue(0).isDefinedAs(Value::Type::BOOL));
957 } else {
958 print_argCnt_warning("is_bool", ctx, evalctx);
959 }
960 return Value::undefined.clone();
961 }
962
builtin_is_string(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)963 Value builtin_is_string(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
964 {
965 if (evalctx->numArgs() == 1) {
966 return Value(evalctx->getArgValue(0).isDefinedAs(Value::Type::STRING));
967 } else {
968 print_argCnt_warning("is_string", ctx, evalctx);
969 }
970 return Value::undefined.clone();
971 }
972
builtin_is_function(const std::shared_ptr<Context> ctx,const std::shared_ptr<EvalContext> evalctx)973 Value builtin_is_function(const std::shared_ptr<Context> ctx, const std::shared_ptr<EvalContext> evalctx)
974 {
975 if (evalctx->numArgs() == 1) {
976 return Value(evalctx->getArgValue(0).isDefinedAs(Value::Type::FUNCTION));
977 } else {
978 print_argCnt_warning("is_function", ctx, evalctx);
979 }
980 return Value::undefined.clone();
981 }
982
register_builtin_functions()983 void register_builtin_functions()
984 {
985 Builtins::init("abs", new BuiltinFunction(&builtin_abs),
986 {
987 "abs(number) -> number",
988 });
989
990 Builtins::init("sign", new BuiltinFunction(&builtin_sign),
991 {
992 "sign(number) -> -1, 0 or 1",
993 });
994
995 Builtins::init("rands", new BuiltinFunction(&builtin_rands),
996 {
997 "rands(min, max, num_results) -> vector",
998 "rands(min, max, num_results, seed) -> vector",
999 });
1000
1001 Builtins::init("min", new BuiltinFunction(&builtin_min),
1002 {
1003 "min(number, number, ...) -> number",
1004 "min(vector) -> number",
1005 });
1006
1007 Builtins::init("max", new BuiltinFunction(&builtin_max),
1008 {
1009 "max(number, number, ...) -> number",
1010 "max(vector) -> number",
1011 });
1012
1013 Builtins::init("sin", new BuiltinFunction(&builtin_sin),
1014 {
1015 "sin(degrees) -> number",
1016 });
1017
1018 Builtins::init("cos", new BuiltinFunction(&builtin_cos),
1019 {
1020 "cos(degrees) -> number",
1021 });
1022
1023 Builtins::init("asin", new BuiltinFunction(&builtin_asin),
1024 {
1025 "asin(number) -> degrees",
1026 });
1027
1028 Builtins::init("acos", new BuiltinFunction(&builtin_acos),
1029 {
1030 "acos(number) -> degrees",
1031 });
1032
1033 Builtins::init("tan", new BuiltinFunction(&builtin_tan),
1034 {
1035 "tan(degrees) -> number",
1036 });
1037
1038 Builtins::init("atan", new BuiltinFunction(&builtin_atan),
1039 {
1040 "atan(number) -> degrees",
1041 });
1042
1043 Builtins::init("atan2", new BuiltinFunction(&builtin_atan2),
1044 {
1045 "atan2(number, number) -> degrees",
1046 });
1047
1048 Builtins::init("round", new BuiltinFunction(&builtin_round),
1049 {
1050 "round(number) -> number",
1051 });
1052
1053 Builtins::init("ceil", new BuiltinFunction(&builtin_ceil),
1054 {
1055 "ceil(number) -> number",
1056 });
1057
1058 Builtins::init("floor", new BuiltinFunction(&builtin_floor),
1059 {
1060 "floor(number) -> number",
1061 });
1062
1063 Builtins::init("pow", new BuiltinFunction(&builtin_pow),
1064 {
1065 "pow(base, exponent) -> number",
1066 });
1067
1068 Builtins::init("sqrt", new BuiltinFunction(&builtin_sqrt),
1069 {
1070 "sqrt(number) -> number",
1071 });
1072
1073 Builtins::init("exp", new BuiltinFunction(&builtin_exp),
1074 {
1075 "exp(number) -> number",
1076 });
1077
1078 Builtins::init("len", new BuiltinFunction(&builtin_length),
1079 {
1080 "len(string) -> number",
1081 "len(vector) -> number",
1082 });
1083
1084 Builtins::init("log", new BuiltinFunction(&builtin_log),
1085 {
1086 "log(number) -> number",
1087 });
1088
1089 Builtins::init("ln", new BuiltinFunction(&builtin_ln),
1090 {
1091 "ln(number) -> number",
1092 });
1093
1094 Builtins::init("str", new BuiltinFunction(&builtin_str),
1095 {
1096 "str(number or string, ...) -> string",
1097 });
1098
1099 Builtins::init("chr", new BuiltinFunction(&builtin_chr),
1100 {
1101 "chr(number) -> string",
1102 "chr(vector) -> string",
1103 "chr(range) -> string",
1104 });
1105
1106 Builtins::init("ord", new BuiltinFunction(&builtin_ord),
1107 {
1108 "ord(string) -> number",
1109 });
1110
1111 Builtins::init("concat", new BuiltinFunction(&builtin_concat),
1112 {
1113 "concat(number or string or vector, ...) -> vector",
1114 });
1115
1116 Builtins::init("lookup", new BuiltinFunction(&builtin_lookup),
1117 {
1118 "lookup(key, <key,value> vector) -> value",
1119 });
1120
1121 Builtins::init("search", new BuiltinFunction(&builtin_search),
1122 {
1123 "search(string , string or vector [, num_returns_per_match [, index_col_num ] ] ) -> vector",
1124 });
1125
1126 Builtins::init("version", new BuiltinFunction(&builtin_version),
1127 {
1128 "version() -> vector",
1129 });
1130
1131 Builtins::init("version_num", new BuiltinFunction(&builtin_version_num),
1132 {
1133 "version_num() -> number",
1134 });
1135
1136 Builtins::init("norm", new BuiltinFunction(&builtin_norm),
1137 {
1138 "norm(vector) -> number",
1139 });
1140
1141 Builtins::init("cross", new BuiltinFunction(&builtin_cross),
1142 {
1143 "cross(vector, vector) -> vector",
1144 });
1145
1146 Builtins::init("parent_module", new BuiltinFunction(&builtin_parent_module),
1147 {
1148 "parent_module(number) -> string",
1149 });
1150
1151 Builtins::init("is_undef", new BuiltinFunction(&builtin_is_undef),
1152 {
1153 "is_undef(arg) -> boolean",
1154 });
1155
1156 Builtins::init("is_list", new BuiltinFunction(&builtin_is_list),
1157 {
1158 "is_list(arg) -> boolean",
1159 });
1160
1161 Builtins::init("is_num", new BuiltinFunction(&builtin_is_num),
1162 {
1163 "is_num(arg) -> boolean",
1164 });
1165
1166 Builtins::init("is_bool", new BuiltinFunction(&builtin_is_bool),
1167 {
1168 "is_bool(arg) -> boolean",
1169 });
1170
1171 Builtins::init("is_string", new BuiltinFunction(&builtin_is_string),
1172 {
1173 "is_string(arg) -> boolean",
1174 });
1175
1176 Builtins::init("is_function", new BuiltinFunction(&builtin_is_function),
1177 {
1178 "is_function(arg) -> boolean",
1179 });
1180 }
1181