forked from AutoHotkey/AutoHotkey
-
Notifications
You must be signed in to change notification settings - Fork 0
/
script_object.cpp
2198 lines (1961 loc) · 72 KB
/
script_object.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
863
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "stdafx.h" // pre-compiled headers
#include "defines.h"
#include "globaldata.h"
#include "script.h"
#include "application.h"
#include "script_object.h"
#include "script_func_impl.h"
//
// Internal: CallFunc - Call a script function with given params.
//
ResultType CallFunc(Func &aFunc, ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
// Caller should pass an aResultToken with the usual setup:
// buf points to a buffer the called function may use: TCHAR[MAX_NUMBER_SIZE]
// mem_to_free is NULL; if it is non-NULL on return, caller (or caller's caller) is responsible for it.
// Caller is responsible for making a persistent copy of the result, if appropriate.
{
if (aParamCount < aFunc.mMinParams)
{
aResultToken.symbol = SYM_STRING;
aResultToken.marker = _T("");
return OK; // Not FAIL, which would cause the entire thread to exit.
}
// When this variable goes out of scope, Var::FreeAndRestoreFunctionVars() is called (if appropriate):
FuncCallData func_call;
ResultType result;
// CALL THE FUNCTION.
if (aFunc.Call(func_call, result, aResultToken, aParam, aParamCount)
// Make return value persistent if applicable:
&& aResultToken.symbol == SYM_STRING && !aFunc.mIsBuiltIn)
{
// Make a persistent copy of the string in case it is the contents of one of the function's local variables.
if ( !*aResultToken.marker || !TokenSetResult(aResultToken, aResultToken.marker) )
// Above failed or the result is an empty string, so make sure it remains valid after we return:
aResultToken.marker = _T("");
}
return result;
}
//
// CallMethod - Invoke a method with no parameters, discarding the result.
//
ResultType CallMethod(IObject *aInvokee, IObject *aThis, LPTSTR aMethodName
, ExprTokenType *aParamValue, int aParamCount, INT_PTR *aRetVal // For event handlers.
, int aExtraFlags) // For Object.__Delete().
{
ExprTokenType result_token, this_token, name_token;
TCHAR result_buf[MAX_NUMBER_SIZE];
result_token.marker = _T("");
result_token.symbol = SYM_STRING;
result_token.mem_to_free = NULL;
result_token.buf = result_buf;
this_token.symbol = SYM_OBJECT;
this_token.object = aThis;
aParamCount; // For the method name.
ExprTokenType **param = (ExprTokenType **)_alloca(aParamCount * sizeof(ExprTokenType *));
name_token.symbol = SYM_STRING;
name_token.marker = aMethodName;
param[0] = &name_token;
for (int i = 1; i < aParamCount; i)
param[i] = aParamValue (i-1);
ResultType result = aInvokee->Invoke(result_token, this_token, IT_CALL | aExtraFlags, param, aParamCount);
if (result != EARLY_EXIT && result != FAIL)
{
// Indicate to caller whether an integer value was returned (for MsgMonitor()).
result = TokenIsEmptyString(result_token) ? OK : EARLY_RETURN;
}
if (aRetVal) // Always set this as some callers don't initialize it:
*aRetVal = result == EARLY_RETURN ? (INT_PTR)TokenToInt64(result_token) : 0;
if (result_token.mem_to_free)
free(result_token.mem_to_free);
if (result_token.symbol == SYM_OBJECT)
result_token.object->Release();
return result;
}
//
// Object::Create - Called by BIF_ObjCreate to create a new object, optionally passing key/value pairs to set.
//
Object *Object::Create(ExprTokenType *aParam[], int aParamCount)
{
if (aParamCount & 1)
return NULL; // Odd number of parameters - reserved for future use.
Object *obj = new Object();
if (obj && aParamCount)
{
ExprTokenType result_token, this_token;
TCHAR buf[MAX_NUMBER_SIZE];
this_token.symbol = SYM_OBJECT;
this_token.object = obj;
for (int i = 0; i 1 < aParamCount; i = 2)
{
if (aParam[i]->symbol == SYM_MISSING || aParam[i 1]->symbol == SYM_MISSING)
continue; // For simplicity.
result_token.symbol = SYM_STRING;
result_token.marker = _T("");
result_token.mem_to_free = NULL;
result_token.buf = buf;
// This is used rather than a more direct approach to ensure it is equivalent to assignment.
// For instance, Object("base",MyBase,"a",1,"b",2) invokes meta-functions contained by MyBase.
// For future consideration: Maybe it *should* bypass the meta-mechanism?
obj->Invoke(result_token, this_token, IT_SET, aParam i, 2);
if (result_token.symbol == SYM_OBJECT) // L33: Bugfix. Invoke must assume the result will be used and as a result we must account for this object reference:
result_token.object->Release();
if (result_token.mem_to_free) // Comment may be obsolete: Currently should never happen, but may happen in future.
free(result_token.mem_to_free);
}
}
return obj;
}
Object *Object::CreateArray(ExprTokenType *aValue[], int aValueCount)
{
Object *obj = new Object();
if (obj && aValueCount && !obj->InsertAt(0, 1, aValue, aValueCount))
{
obj->Release(); // InsertAt failed.
obj = NULL;
}
return obj;
}
//
// Object::Clone - Used for variadic function-calls.
//
Object *Object::Clone(BOOL aExcludeIntegerKeys)
// Creates an object and copies to it the fields at and after the given offset.
{
IndexType aStartOffset = aExcludeIntegerKeys ? mKeyOffsetObject : 0;
Object *objptr = new Object();
if (!objptr|| aStartOffset >= mFieldCount)
return objptr;
Object &obj = *objptr;
// Allocate space in destination object.
IndexType field_count = mFieldCount - aStartOffset;
if (!obj.SetInternalCapacity(field_count))
{
obj.Release();
return NULL;
}
FieldType *fields = obj.mFields; // Newly allocated by above.
int failure_count = 0; // See comment below.
IndexType i;
obj.mFieldCount = field_count;
obj.mKeyOffsetObject = mKeyOffsetObject - aStartOffset;
obj.mKeyOffsetString = mKeyOffsetString - aStartOffset;
if (obj.mKeyOffsetObject < 0) // Currently might always evaluate to false.
{
obj.mKeyOffsetObject = 0; // aStartOffset excluded all integer and some or all object keys.
if (obj.mKeyOffsetString < 0)
obj.mKeyOffsetString = 0; // aStartOffset also excluded some string keys.
}
//else no need to check mKeyOffsetString since it should always be >= mKeyOffsetObject.
for (i = 0; i < field_count; i)
{
FieldType &dst = fields[i];
FieldType &src = mFields[aStartOffset i];
// Copy key.
if (i >= obj.mKeyOffsetString)
{
if ( !(dst.key.s = _tcsdup(src.key.s)) )
{
// Key allocation failed. At this point, all int and object keys
// have been set and values for previous fields have been copied.
// Rather than trying to set up the object so that what we have
// so far is valid in order to break out of the loop, continue,
// make all fields valid and then allow them to be freed.
failure_count;
}
}
else if (i >= obj.mKeyOffsetObject)
(dst.key.p = src.key.p)->AddRef();
else
dst.key.i = src.key.i;
// Copy value.
switch (dst.symbol = src.symbol)
{
case SYM_OPERAND:
if (dst.size = src.size)
{
if (dst.marker = tmalloc(dst.size))
{
// Since user may have stored binary data, copy the entire field:
tmemcpy(dst.marker, src.marker, src.size);
continue;
}
// Since above didn't continue: allocation failed.
failure_count; // See failure comment further above.
}
dst.marker = Var::sEmptyString;
dst.size = 0;
break;
case SYM_OBJECT:
(dst.object = src.object)->AddRef();
break;
//case SYM_INTEGER:
//case SYM_FLOAT:
default:
dst.n_int64 = src.n_int64; // Union copy.
}
}
if (failure_count)
{
// One or more memory allocations failed. It seems best to return a clear failure
// indication rather than an incomplete copy. Now that the loop above has finished,
// the object's contents are at least valid and it is safe to free the object:
obj.Release();
return NULL;
}
return &obj;
}
//
// Object::ArrayToParams - Used for variadic function-calls.
//
void Object::ArrayToParams(ExprTokenType *token, ExprTokenType **param_list, int extra_params
, ExprTokenType **aParam, int aParamCount)
// Expands this object's contents into the parameter list. Due to the nature
// of the parameter list, only fields with integer keys are used (named params
// aren't supported).
// Return value is FAIL if a required parameter was omitted or malloc() failed.
{
// Find the first and last field to be used.
int start = (int)mKeyOffsetInt;
int end = (int)mKeyOffsetObject; // For readability.
while (start < end && mFields[start].key.i < 1)
start; // Skip any keys <= 0 (consistent with UDF-calling behaviour).
int param_index;
IndexType field_index;
// For each extra param...
for (field_index = start, param_index = 0; field_index < end; field_index, param_index)
{
for ( ; param_index 1 < (int)mFields[field_index].key.i; param_index)
{
token[param_index].symbol = SYM_MISSING;
token[param_index].marker = _T("");
}
mFields[field_index].ToToken(token[param_index]);
}
ExprTokenType **param_ptr = param_list;
// Init the array of param token pointers.
for (param_index = 0; param_index < aParamCount; param_index)
*param_ptr = aParam[param_index]; // Caller-supplied param token.
for (param_index = 0; param_index < extra_params; param_index)
*param_ptr = &token[param_index]; // New param.
}
//
// Object::ArrayToStrings - Used by BIF_StrSplit.
//
ResultType Object::ArrayToStrings(LPTSTR *aStrings, int &aStringCount, int aStringsMax)
{
int i, j;
for (i = 0, j = 0; i < aStringsMax && j < mKeyOffsetObject; j)
if (SYM_OPERAND == mFields[j].symbol)
aStrings[i ] = mFields[j].marker;
else
return FAIL;
aStringCount = i;
return OK;
}
//
// Object::Delete - Called immediately before the object is deleted.
// Returns false if object should not be deleted yet.
//
bool Object::Delete()
{
if (mBase)
{
KeyType key;
IndexType insert_pos;
key.s = _T("__Class");
if (FindField(SYM_STRING, key, insert_pos))
// This object appears to be a class definition, so it would probably be
// undesirable to call the super-class' __Delete() meta-function for this.
return ObjectBase::Delete();
// L33: Privatize the last recursion layer's deref buffer in case it is in use by our caller.
// It's done here rather than in Var::FreeAndRestoreFunctionVars or CallFunc (even though the
// below might not actually call any script functions) because this function is probably
// executed much less often in most cases.
PRIVATIZE_S_DEREF_BUF;
// If an exception has been thrown, temporarily clear it for execution of __Delete.
ExprTokenType *exc = g->ThrownToken;
g->ThrownToken = NULL;
// This prevents an erroneous "The current thread will exit" message when an error occurs,
// by causing LineError() to throw an exception:
bool in_try = g->InTryBlock;
g->InTryBlock = true;
CallMethod(mBase, this, sMetaFuncName[3], NULL, 0, NULL, IF_METAOBJ); // base.__Delete()
g->InTryBlock = in_try;
// Exceptions thrown by __Delete are reported immediately because they would not be handled
// consistently by the caller (they would typically be "thrown" by the next function call),
// and because the caller must be allowed to make additional __Delete calls.
if (g->ThrownToken)
g_script.UnhandledException(g->ThrownToken, NULL, _T("__Delete will now return."));
// If an exception has been thrown by our caller, it's likely that it can and should be handled
// reliably by our caller, so restore it.
if (exc)
g->ThrownToken = exc;
DEPRIVATIZE_S_DEREF_BUF; // L33: See above.
// Above may pass the script a reference to this object to allow cleanup routines to free any
// associated resources. Deleting it is only safe if the script no longer holds any references
// to it. Since cleanup routines may (intentionally or unintentionally) copy this reference,
// ensure this object really has no more references before proceeding with deletion:
if (mRefCount > 1)
return false;
}
return ObjectBase::Delete();
}
Object::~Object()
{
if (mBase)
mBase->Release();
if (mFields)
{
if (mFieldCount)
{
IndexType i = mFieldCount - 1;
// Free keys: first strings, then objects (objects have a lower index in the mFields array).
for ( ; i >= mKeyOffsetString; --i)
free(mFields[i].key.s);
for ( ; i >= mKeyOffsetObject; --i)
mFields[i].key.p->Release();
// Free values.
while (mFieldCount)
mFields[--mFieldCount].Free();
}
// Free fields array.
free(mFields);
}
}
//
// Object::Invoke - Called by BIF_ObjInvoke when script explicitly interacts with an object.
//
ResultType STDMETHODCALLTYPE Object::Invoke(
ExprTokenType &aResultToken,
ExprTokenType &aThisToken,
int aFlags,
ExprTokenType *aParam[],
int aParamCount
)
// L40: Revised base mechanism for flexibility and to simplify some aspects.
// obj[] -> obj.base.__Get -> obj.base[] -> obj.base.__Get etc.
{
SymbolType key_type;
KeyType key;
FieldType *field, *prop_field;
IndexType insert_pos;
Property *prop = NULL; // Set default.
// If this is some object's base and is being invoked in that capacity, call
// __Get/__Set/__Call as defined in this base object before searching further.
if (SHOULD_INVOKE_METAFUNC)
{
key.s = sMetaFuncName[INVOKE_TYPE];
// Look for a meta-function definition directly in this base object.
if (field = FindField(SYM_STRING, key, /*out*/ insert_pos))
{
// Seems more maintainable to copy params rather than assume aParam[-1] is always valid.
ExprTokenType **meta_params = (ExprTokenType **)_alloca((aParamCount 1) * sizeof(ExprTokenType *));
// Shallow copy; points to the same tokens. Leave a space for param[0], which must be the param which
// identified the field (or in this case an empty space) to replace with aThisToken when appropriate.
memcpy(meta_params 1, aParam, aParamCount * sizeof(ExprTokenType*));
Line *curr_line = g_script.mCurrLine;
ResultType r = CallField(field, aResultToken, aThisToken, aFlags, meta_params, aParamCount 1);
g_script.mCurrLine = curr_line; // Allows exceptions thrown by later meta-functions to report a more appropriate line.
//if (r == EARLY_RETURN)
// Propagate EARLY_RETURN in case this was the __Call meta-function of a
// "function object" which is used as a meta-function of some other object.
//return EARLY_RETURN; // TODO: Detection of 'return' vs 'return empty_value'.
if (r != OK) // Likely EARLY_RETURN, FAIL or EARLY_EXIT.
return r;
}
}
int param_count_excluding_rvalue = aParamCount;
if (IS_INVOKE_SET)
{
// Due to the way expression parsing works, the result should never be negative
// (and any direct callers of Invoke must always pass aParamCount >= 1):
--param_count_excluding_rvalue;
}
if (param_count_excluding_rvalue && aParam[0]->symbol != SYM_MISSING)
{
field = FindField(*aParam[0], aResultToken.buf, /*out*/ key_type, /*out*/ key, /*out*/ insert_pos);
// There used to be a check prior to the FindField() call above which avoided searching
// for base[field] when performing an assignment. As an unintended side-effect, it was
// possible for base.base.__Set to be called even if base[field] exists, which is
// inconsistent with __Get and __Call. That check was removed for v1.1.16 in order
// to implement property accessors, and a check was added below to retain the old
// behaviour for compatibility -- this should be changed in v2.
static Property sProperty;
// v1.1.16: Handle class property accessors:
if (field && field->symbol == SYM_OBJECT && *(void **)field->object == *(void **)&sProperty)
{
// The "type check" above is used for speed. Simple benchmarks of x[1] where x := [[]]
// shows this check to not affect performance, whereas dynamic_cast<> hurt performance by
// about 25% and typeid()== by about 20%. We can safely assume that the vtable pointer is
// stored at the beginning of the object even though it isn't guaranteed by the C standard,
// since COM fundamentally requires it: http://msdn.microsoft.com/en-us/library/dd757710
prop = (Property *)field->object;
prop_field = field;
if (IS_INVOKE_SET ? prop->CanSet() : prop->CanGet())
{
if (aParamCount > 2 && IS_INVOKE_SET)
{
// Do some shuffling to put value before the other parameters. This relies on above
// having verified that we're handling this invocation; otherwise the parameters would
// need to be swapped back later in case they're passed to a base's meta-function.
ExprTokenType *value = aParam[aParamCount - 1];
for (int i = aParamCount - 1; i > 1; --i)
aParam[i] = aParam[i - 1];
aParam[1] = value; // Corresponds to the setter's hidden "value" parameter.
}
ExprTokenType *name_token = aParam[0];
aParam[0] = &aThisToken; // For the hidden "this" parameter in the getter/setter.
// Pass IF_FUNCOBJ so that it'll pass all parameters to the getter/setter.
// For a functor Object, we would need to pass a token representing "this" Property,
// but since Property::Invoke doesn't use it, we pass our aThisToken for simplicity.
ResultType result = prop->Invoke(aResultToken, aThisToken, aFlags | IF_FUNCOBJ, aParam, aParamCount);
aParam[0] = name_token;
return result == EARLY_RETURN ? OK : result;
}
// The property was missing get/set (whichever this invocation is), so continue as
// if the property itself wasn't defined.
field = NULL;
}
else if (IS_INVOKE_META && IS_INVOKE_SET && param_count_excluding_rvalue == 1) // v1.1.16: For compatibility with earlier versions - see above.
{
key_type = SYM_INVALID;
field = NULL;
}
}
else
{
key_type = SYM_INVALID; // Allow key_type checks below without requiring that param_count_excluding_rvalue also be checked.
field = NULL;
}
if (!field)
{
// This field doesn't exist, so let our base object define what happens:
// 1) __Get, __Set or __Call. If these don't return a value, processing continues.
// 2) For GET and CALL only, check the base object's own fields.
// 3) Repeat 1 through 3 for the base object's own base.
if (mBase)
{
// aFlags: If caller specified IF_METAOBJ but not IF_METAFUNC, they want to recursively
// find and execute a specific meta-function (__new or __delete) but don't want any base
// object to invoke __call. So if this is already a meta-invocation, don't change aFlags.
ResultType r = mBase->Invoke(aResultToken, aThisToken, aFlags | (IS_INVOKE_META ? 0 : IF_META), aParam, aParamCount);
if (r != INVOKE_NOT_HANDLED // Base handled it.
|| key_type == SYM_INVALID) // Nothing left to do in this case.
return r;
// Since the above may have inserted or removed fields (including the specified one),
// insert_pos may no longer be correct or safe. Updating field also allows a meta-function
// to initialize a field and allow processing to continue as if it already existed.
field = FindField(key_type, key, /*out*/ insert_pos);
if (prop)
{
// This field was a property.
if (field && field->symbol == SYM_OBJECT && field->object == prop)
{
// This field is still a property (and the same one).
prop_field = field; // Must update this pointer in case the field is to be overwritten.
field = NULL; // Act like the field doesn't exist (until the time comes to insert a value).
}
else
prop = NULL; // field was reassigned or removed, so ignore the property.
}
}
// Since the base object didn't handle this op, check for built-in properties/methods.
// This must apply only to the original target object (aThisToken), not one of its bases.
if (!IS_INVOKE_META && key_type == SYM_STRING && !field) // v1.1.16: Check field again so if __Call sets a field, it gets called.
{
//
// BUILT-IN METHODS
//
if (IS_INVOKE_CALL)
{
// Since above has not handled this call and no field exists,
// it can only be a built-in method or unknown. This handles both:
return CallBuiltin(GetBuiltinID(key.s), aResultToken, aParam 1, aParamCount - 1); // /- 1 to exclude the method identifier.
}
//
// BUILT-IN "BASE" PROPERTY
//
else if (param_count_excluding_rvalue == 1 && !_tcsicmp(key.s, _T("base")))
{
if (IS_INVOKE_SET)
// "base" must be handled before inserting a new field.
{
IObject *obj = TokenToObject(*aParam[1]);
if (obj)
{
obj->AddRef(); // for mBase
obj->AddRef(); // for aResultToken
aResultToken.symbol = SYM_OBJECT;
aResultToken.object = obj;
}
// else leave as empty string.
if (mBase)
mBase->Release();
mBase = obj; // May be NULL.
return OK;
}
else // GET
{
if (mBase)
{
aResultToken.symbol = SYM_OBJECT;
aResultToken.object = mBase;
mBase->AddRef();
}
// else leave as empty string.
return OK;
}
}
} // if (!IS_INVOKE_META && key_type == SYM_STRING)
} // if (!field)
//
// OPERATE ON A FIELD WITHIN THIS OBJECT
//
// CALL
if (IS_INVOKE_CALL)
{
if (!field)
return INVOKE_NOT_HANDLED;
// v1.1.18: The following flag is set whenever a COM client invokes with METHOD|PROPERTYGET,
// such as X.Y in VBScript or C#. Some convenience is gained at the expense of purity by treating
// it as METHOD if X.Y is a Func object or PROPERTYGET in any other case.
// v1.1.19: Handling this flag here rather than in CallField() has the following benefits:
// - Reduces code duplication.
// - Fixes X.__Call being returned instead of being called, if X.__Call is a string.
// - Allows X.Y(Z) and similar to work like X.Y[Z], instead of ignoring the extra parameters.
if ( !(aFlags & IF_CALL_FUNC_ONLY) || (field->symbol == SYM_OBJECT && dynamic_cast<Func *>(field->object)) )
return CallField(field, aResultToken, aThisToken, aFlags, aParam, aParamCount);
aFlags = (aFlags & ~(IT_BITMASK | IF_CALL_FUNC_ONLY)) | IT_GET;
}
// MULTIPARAM[x,y] -- may be SET[x,y]:=z or GET[x,y], but always treated like GET[x].
if (param_count_excluding_rvalue > 1)
{
// This is something like this[x,y] or this[x,y]:=z. Since it wasn't handled by a meta-mechanism above,
// handle only the "x" part (automatically creating and storing an object if this[x] didn't already exist
// and an assignment is being made) and recursively invoke. This has at least two benefits:
// 1) Objects natively function as multi-dimensional arrays.
// 2) Automatic initialization of object-fields.
// For instance, this["base","__Get"]:="MyObjGet" does not require a prior this.base:=Object().
IObject *obj = NULL;
if (field)
{
if (field->symbol == SYM_OBJECT)
// AddRef not used. See below.
obj = field->object;
}
else if (!IS_INVOKE_META)
{
// This section applies only to the target object (aThisToken) and not any of its base objects.
// Allow obj["base",x] to access a field of obj.base; L40: This also fixes obj.base[x] which was broken by L36.
if (key_type == SYM_STRING && !_tcsicmp(key.s, _T("base")))
{
if (!mBase && IS_INVOKE_SET)
mBase = new Object();
obj = mBase; // If NULL, above failed and below will detect it.
}
// Automatically create a new object for the x part of obj[x,y]:=z.
else if (IS_INVOKE_SET)
{
Object *new_obj = new Object();
if (new_obj)
{
if ( field = prop ? prop_field : Insert(key_type, key, insert_pos) )
{
if (prop) // Otherwise, field is already empty.
prop->Release();
// Don't do field->Assign() since it would do AddRef() and we would need to counter with Release().
field->symbol = SYM_OBJECT;
field->object = obj = new_obj;
}
else
{ // Create() succeeded but Insert() failed, so free the newly created obj.
new_obj->Release();
}
}
}
}
if (obj) // Object was successfully found or created.
{
// obj now contains a pointer to the object contained by this field, possibly newly created above.
ExprTokenType obj_token;
obj_token.symbol = SYM_OBJECT;
obj_token.object = obj;
// References in obj_token and obj weren't counted (AddRef wasn't called), so Release() does not
// need to be called before returning, and accessing obj after calling Invoke() would not be safe
// since it could Release() the object (by overwriting our field via script) as a side-effect.
// Recursively invoke obj, passing remaining parameters; remove IF_META to correctly treat obj as target:
return obj->Invoke(aResultToken, obj_token, aFlags & ~IF_META, aParam 1, aParamCount - 1);
// Above may return INVOKE_NOT_HANDLED in cases such as obj[a,b] where obj[a] exists but obj[a][b] does not.
}
} // MULTIPARAM[x,y]
// SET
else if (IS_INVOKE_SET)
{
if (!IS_INVOKE_META && param_count_excluding_rvalue)
{
ExprTokenType &value_param = *aParam[1];
// L34: Assigning an empty string no longer removes the field.
if ( (field || (field = prop ? prop_field : Insert(key_type, key, insert_pos)))
&& field->Assign(value_param) )
{
if (field->symbol == SYM_OPERAND)
{
// Use value_param since our copy may be freed prematurely in some (possibly rare) cases:
aResultToken.symbol = SYM_STRING;
aResultToken.marker = TokenToString(value_param);
// Below: no longer used as other areas expect string results to always be SYM_STRING.
// If it is ever used in future, we MUST copy marker and buf separately for x64 support.
// However, it seems appropriate *not* to return a SYM_OPERAND with cached binary integer
// since above has stored only the string part of it.
//aResultToken.symbol = value_param.symbol;
//aResultToken.value_int64 = value_param.value_int64; // Copy marker and buf (via union) in case it is SYM_OPERAND with a cached integer.
}
else
field->Get(aResultToken); // L34: Corrected this to be aResultToken instead of value_param (broken by L33).
}
return OK;
}
}
// GET
else if (field)
{
if (field->symbol == SYM_OPERAND)
{
// Use SYM_STRING and not SYM_OPERAND, since SYM_OPERAND's use of aResultToken.buf
// would conflict with the use of mem_to_free/buf to return a memory allocation.
aResultToken.symbol = SYM_STRING;
// L33: Make a persistent copy; our copy might be freed indirectly by releasing this object.
// Prior to L33, callers took care of this UNLESS this was the last op in an expression.
if (!TokenSetResult(aResultToken, field->marker))
aResultToken.marker = _T("");
}
else
field->Get(aResultToken);
return OK;
}
return INVOKE_NOT_HANDLED;
}
int Object::GetBuiltinID(LPCTSTR aName)
{
// Newer methods which do not support the _ prefix:
switch (toupper(*aName))
{
case 'L':
if (!_tcsicmp(aName, _T("Length")))
return FID_ObjLength;
break;
case 'P':
if (!_tcsicmp(aName, _T("Push")))
return FID_ObjPush;
if (!_tcsicmp(aName, _T("Pop")))
return FID_ObjPop;
break;
case 'I':
if (!_tcsicmp(aName, _T("InsertAt")))
return FID_ObjInsertAt;
break;
case 'R':
if (!_tcsicmp(aName, _T("RemoveAt")))
return FID_ObjRemoveAt;
break;
case 'D':
if (!_tcsicmp(aName, _T("Delete")))
return FID_ObjDelete;
break;
}
// Older methods which support the _ prefix:
if (*aName == '_')
aName; // Exclude the prefix from further consideration.
switch (toupper(*aName))
{
case 'H':
if (!_tcsicmp(aName, _T("HasKey")))
return FID_ObjHasKey;
break;
case 'N':
if (!_tcsicmp(aName, _T("NewEnum")))
return FID_ObjNewEnum;
break;
case 'G':
if (!_tcsicmp(aName, _T("GetAddress")))
return FID_ObjGetAddress;
if (!_tcsicmp(aName, _T("GetCapacity")))
return FID_ObjGetCapacity;
break;
case 'S':
if (!_tcsicmp(aName, _T("SetCapacity")))
return FID_ObjSetCapacity;
break;
case 'C':
if (!_tcsicmp(aName, _T("Clone")))
return FID_ObjClone;
break;
case 'M':
if (!_tcsicmp(aName, _T("MaxIndex")))
return FID_ObjMaxIndex;
if (!_tcsicmp(aName, _T("MinIndex")))
return FID_ObjMinIndex;
break;
// Deprecated methods:
case 'I':
if (!_tcsicmp(aName, _T("Insert")))
return FID_ObjInsert;
break;
case 'R':
if (!_tcsicmp(aName, _T("Remove")))
return FID_ObjRemove;
break;
}
return -1;
}
ResultType Object::CallBuiltin(int aID, ExprTokenType &aResultToken, ExprTokenType *aParam[], int aParamCount)
{
switch (aID)
{
#define case_method(name) \
case FID_Obj##name: \
return _##name(aResultToken, aParam, aParamCount)
// Putting more frequently called methods first might help performance.
case_method(Length);
case_method(HasKey);
case_method(MaxIndex);
case_method(NewEnum);
case_method(Push);
case_method(Pop);
case_method(InsertAt);
case_method(RemoveAt);
case_method(Delete);
case_method(MinIndex);
case_method(GetAddress);
case_method(SetCapacity);
case_method(GetCapacity);
case_method(Clone);
// Deprecated methods:
case_method(Insert);
case_method(Remove);
#undef case_method
}
return INVOKE_NOT_HANDLED;
}
//
// Helper function for WinMain()
//
Object *Object::CreateFromArgV(LPTSTR *aArgV, int aArgC)
{
ExprTokenType *token = (ExprTokenType *)_alloca(aArgC * sizeof(ExprTokenType));
ExprTokenType **param = (ExprTokenType **)_alloca(aArgC * sizeof(ExprTokenType*));
for (int j = 0; j < aArgC; j)
{
token[j].SetValue(aArgV[j]);
param[j] = &token[j];
}
return CreateArray(param, aArgC);
}
//
// Internal: Object::CallField - Used by Object::Invoke to call a function/method stored in this object.
//
ResultType Object::CallField(FieldType *aField, ExprTokenType &aResultToken, ExprTokenType &aThisToken, int aFlags, ExprTokenType *aParam[], int aParamCount)
// aParam[0] contains the identifier of this field or an empty space (for __Get etc.).
{
if (aField->symbol == SYM_OBJECT)
{
ExprTokenType field_token;
field_token.symbol = SYM_OBJECT;
field_token.object = aField->object;
ExprTokenType *tmp = aParam[0];
// Something must be inserted into the parameter list to remove any ambiguity between an intentionally
// and directly called function of 'that' object and one of our parameters matching an existing name.
// Rather than inserting something like an empty string, it seems more useful to insert 'this' object,
// allowing 'that' to change (via __Call) the behaviour of a "function-call" which operates on 'this'.
// Consequently, if 'that[this]' contains a value, it is invoked; seems obscure but rare, and could
// also be of use (for instance, as a means to remove the 'this' parameter or replace it with 'that').
aParam[0] = &aThisToken;
ResultType r = aField->object->Invoke(aResultToken, field_token, IT_CALL | IF_FUNCOBJ, aParam, aParamCount);
aParam[0] = tmp;
return r;
}
if (aField->symbol == SYM_OPERAND)
{
Func *func = g_script.FindFunc(aField->marker);
if (func)
{
// At this point, aIdCount == 1 and aParamCount includes only the explicit parameters for the call.
if (IS_INVOKE_META)
{
ExprTokenType *tmp = aParam[0];
// Called indirectly by means of the meta-object mechanism (mBase); treat it as a "method call".
// For this type of call, "this" object is included as the first parameter. To do this, aParam[0] is
// temporarily overwritten with a pointer to aThisToken. Note that aThisToken contains the original
// object specified in script, not the real "this" which is actually a meta-object/base of that object.
aParam[0] = &aThisToken;
ResultType r = CallFunc(*func, aResultToken, aParam, aParamCount);
aParam[0] = tmp;
return r;
}
else
// This object directly contains a function name. Assume this object is intended
// as a simple array of functions; do not pass aThisToken as is done above.
// aParam 1 vs aParam because aParam[0] is the key which was used to find this field, not a parameter of the call.
return CallFunc(*func, aResultToken, aParam 1, aParamCount - 1);
}
}
return INVOKE_NOT_HANDLED;
}
//
// Helper function for StringSplit()
//
bool Object::Append(LPTSTR aValue, size_t aValueLength)
{
if (mFieldCount == mFieldCountMax && !Expand()) // Attempt to expand if at capacity.
return false;
if (aValueLength == -1)
aValueLength = _tcslen(aValue);
FieldType &field = mFields[mKeyOffsetObject];
if (mKeyOffsetObject < mFieldCount)
// For maintainability. This might never be done, because our caller
// doesn't use string/object keys. Move existing fields to make room:
memmove(&field 1, &field, (mFieldCount - mKeyOffsetObject) * sizeof(FieldType));
mFieldCount; // Only after memmove above.
mKeyOffsetObject;
mKeyOffsetString;
// The following relies on the fact that callers of this function ONLY use
// this function, so the last integer key == the number of integer keys.
field.key.i = mKeyOffsetObject;
field.symbol = SYM_OPERAND;
if (aValueLength) // i.e. a non-empty string was supplied.
{
aValueLength; // Convert length to size.
if (field.marker = tmalloc(aValueLength))
{
tmemcpy(field.marker, aValue, aValueLength);
field.marker[aValueLength-1] = '\0';
field.size = aValueLength;
return true;
}
// Otherwise, mem alloc failed; assign an empty string.
}
field.marker = Var::sEmptyString;
field.size = 0;
return (aValueLength == 0); // i.e. true if caller supplied an empty string.
}
//
// Helper function used with class definitions.
//
void Object::EndClassDefinition()
{
// Instance variables were previously created as keys in the class object to prevent duplicate or
// conflicting declarations. Since these variables will be added at run-time to the derived objects,
// we don't want them in the class object. So delete any key-value pairs with the special marker
// value (currently any integer, since static initializers haven't been evaluated yet).
for (IndexType i = mFieldCount - 1; i >= 0; --i)
if (mFields[i].symbol == SYM_INTEGER)
{
if (i >= mKeyOffsetString) // Must be checked since key can be an integer, such as for "0 := (expr)".
free(mFields[i].key.s);
if (i < --mFieldCount)
memmove(mFields i, mFields i 1, (mFieldCount - i) * sizeof(FieldType));
}
}
//
// Object::Type() - Returns the object's type/class name.
//
LPTSTR Object::Type()
{
IObject *ibase;
Object *base;
ExprTokenType value;
if (GetItem(value, _T("__Class")))
return _T("Class"); // This object is a class.
for (ibase = mBase; base = dynamic_cast<Object *>(ibase); ibase = base->mBase)
if (base->GetItem(value, _T("__Class")))
return TokenToString(value); // This object is an instance of base.
return _T("Object"); // This is an Object of undetermined type, like Object(), {} or [].
}
//
// Object:: Built-in Methods
//
bool Object::InsertAt(INT_PTR aOffset, INT_PTR aKey, ExprTokenType *aValue[], int aValueCount)
{
IndexType actual_count = (IndexType)aValueCount;
for (int i = 0; i < aValueCount; i)
if (aValue[i]->symbol == SYM_MISSING)
actual_count--;
IndexType need_capacity = mFieldCount actual_count;
if (need_capacity > mFieldCountMax && !SetInternalCapacity(need_capacity))
// Fail.
return false;