-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
p44script.hpp
3831 lines (2988 loc) · 179 KB
/
p44script.hpp
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
594
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
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Copyright (c) 2017-2024 plan44.ch / Lukas Zeller, Zurich, Switzerland
//
// Author: Lukas Zeller <[email protected]>
//
// This file is part of p44utils.
//
// p44utils is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// p44utils is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with p44utils. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef __p44utils__p44script__
#define __p44utils__p44script__
#include "p44utils_main.hpp"
#if ENABLE_P44SCRIPT
#include "timeutils.hpp"
#include <string>
#include <set>
#ifndef P44SCRIPT_FULL_SUPPORT
#define P44SCRIPT_FULL_SUPPORT 1 // on by default, can be switched off for small targets only needing expressions
#endif
#ifndef SCRIPTING_JSON_SUPPORT
#define SCRIPTING_JSON_SUPPORT 1 // on by default
#endif
#ifndef P44SCRIPT_REGISTERED_SOURCE
#define P44SCRIPT_REGISTERED_SOURCE P44SCRIPT_FULL_SUPPORT // on for full script support
#endif
#ifndef P44SCRIPT_MIGRATE_TO_DOMAIN_SOURCE
#define P44SCRIPT_MIGRATE_TO_DOMAIN_SOURCE P44SCRIPT_REGISTERED_SOURCE // include migration when we have registered sources
#endif
#ifndef P44SCRIPT_DEBUGGING_SUPPORT
#define P44SCRIPT_DEBUGGING_SUPPORT P44SCRIPT_FULL_SUPPORT // on for full script support
#endif
#ifndef P44SCRIPT_OTHER_SOURCES
#define P44SCRIPT_OTHER_SOURCES P44SCRIPT_FULL_SUPPORT // also support editing non-script sources when we have full script support
#endif
#if SCRIPTING_JSON_SUPPORT
#include "jsonobject.hpp"
#endif
#ifndef P44SCRIPT_DATA_SUBDIR
#define P44SCRIPT_DATA_SUBDIR "p44script"
#endif
#ifndef P44SCRIPT_INCLUDE_SUBDIR
#define P44SCRIPT_INCLUDE_SUBDIR "include"
#endif
using namespace std;
namespace p44 { namespace P44Script {
// MARK: - class and smart pointer forward definitions
class ScriptObj;
typedef boost::intrusive_ptr<ScriptObj> ScriptObjPtr;
class ValueIterator;
typedef boost::intrusive_ptr<ValueIterator> ValueIteratorPtr;
class ErrorValue;
typedef boost::intrusive_ptr<ErrorValue> ErrorValuePtr;
class NumericValue;
class StringValue;
class StructuredLookupObject;
class ImplementationObj;
class CompiledCode;
typedef boost::intrusive_ptr<CompiledCode> CompiledCodePtr;
class CompiledFunction;
typedef boost::intrusive_ptr<CompiledFunction> CompiledFunctionPtr;
class CompiledScript;
typedef boost::intrusive_ptr<CompiledScript> CompiledScriptPtr;
class CompiledTrigger;
typedef boost::intrusive_ptr<CompiledTrigger> CompiledTriggerPtr;
#if P44SCRIPT_FULL_SUPPORT
class CompiledHandler;
typedef boost::intrusive_ptr<CompiledHandler> CompiledHandlerPtr;
#endif // P44SCRIPT_FULL_SUPPORT
class StructuredLookupObject;
typedef boost::intrusive_ptr<StructuredLookupObject> StructuredLookupObjectPtr;
class ExecutionContext;
typedef boost::intrusive_ptr<ExecutionContext> ExecutionContextPtr;
class ScriptCodeContext;
typedef boost::intrusive_ptr<ScriptCodeContext> ScriptCodeContextPtr;
class ScriptMainContext;
typedef boost::intrusive_ptr<ScriptMainContext> ScriptMainContextPtr;
class ScriptingDomain;
typedef boost::intrusive_ptr<ScriptingDomain> ScriptingDomainPtr;
class SourcePos;
class SourceCursor;
class SourceContainer;
typedef boost::intrusive_ptr<SourceContainer> SourceContainerPtr;
class SourceHost;
typedef boost::intrusive_ptr<SourceHost> SourceHostPtr;
class ScriptIncludeHost;
typedef boost::intrusive_ptr<ScriptIncludeHost> ScriptIncludeHostPtr;
class ScriptHost;
typedef boost::intrusive_ptr<ScriptHost> ScriptHostPtr;
class SourceProcessor;
class Compiler;
class ScriptCodeThread;
typedef boost::intrusive_ptr<ScriptCodeThread> ScriptCodeThreadPtr;
class MemberLookup;
typedef boost::intrusive_ptr<MemberLookup> MemberLookupPtr;
class EventSource;
class EventSink;
// MARK: - Scripting Error
/// Script Error
class ScriptError : public Error
{
public:
// Errors
typedef enum {
OK,
// Catchable errors
User, ///< user generated error (with throw)
DivisionByZero,
CyclicReference,
Invalid, ///< invalid value
NotFound, ///< referenced object not found at runtime (and cannot be created)
NotCreated, ///< object/field does not exist and cannot be created, either
Immutable, ///< object/field exists but is immutable and cannot be assigned
NotCallable, ///< object cannot be called as a function
NotLvalue, ///< object is not an LValue and can't be assigned to
NoPrivilege, ///< no privilege for requested action
Busy, ///< currently running
// Fatal errors, cannot be catched
FatalErrors,
Syntax = FatalErrors, ///< script syntax error
Aborted, ///< externally aborted
Timeout, ///< aborted because max execution time limit reached
AsyncNotAllowed, ///< async executable encountered during synchronous execution
WrongContext, ///< wrong context for this function/statement/operation
Internal, ///< internal inconsistency
numErrorCodes
} ErrorCodes;
static const char *domain() { return "ScriptError"; }
virtual const char *getErrorDomain() const P44_OVERRIDE { return ScriptError::domain(); };
ScriptError(ErrorCodes aError) : Error(ErrorCode(aError)) {};
/// factory method to create string error fprint style
static ErrorPtr err(ErrorCodes aErrCode, const char *aFmt, ...) __printflike(2,3);
#if ENABLE_NAMED_ERRORS
protected:
virtual const char* errorName() const P44_OVERRIDE { return errNames[getErrorCode()]; };
private:
static constexpr const char* const errNames[numErrorCodes] = {
"OK",
"User",
"DivisionByZero",
"CyclicReference",
"Invalid",
"NotFound",
"NotCreated",
"Immutable",
"NotCallable",
"NotLvalue",
"NoPrivilege",
"Busy",
"Syntax",
"Aborted",
"Timeout",
"AsyncNotAllowed",
"WrongContext",
"Internal",
};
#endif // ENABLE_NAMED_ERRORS
};
// MARK: - Script namespace types
/// Evaluation flags
enum {
inherit = 0, ///< for ScriptHost::run() only: no flags, inherit from compile flags
// run mode
runModeMask = 0x00FF,
regular = 0x01, ///< regular script or expression code
initial = 0x02, ///< initial trigger expression run (no external event, implicit event is startup or code change)
triggered = 0x04, ///< externally triggered (re-)evaluation
timed = 0x08, ///< timed evaluation by timed retrigger
scanning = 0x40, ///< scanning only (compiling)
checking = 0x80, ///< scanning everything (for finding syntax errors early)
// scope modifiers
scopeMask = 0xF00,
expression = 0x100, ///< evaluate as an expression (no flow control, variable assignments, blocks etc.)
#if P44SCRIPT_FULL_SUPPORT
scriptbody = 0x200, ///< evaluate as script body (no function or handler definitions)
sourcecode = 0x400, ///< evaluate as script (include parsing functions and handlers)
block = 0x800, ///< evaluate as a block (complete when reaching end of block)
#endif // P44SCRIPT_FULL_SUPPORT
// execution and compilation modifiers
execModifierMask = 0xFFFF000,
synchronously = 0x001000, ///< evaluate synchronously, error out on async code
stoprunning = 0x002000, ///< abort running execution in the same context before starting a new one
queue = 0x004000, ///< queue for execution (with concurrently also set, thread will start when all previously queued threads are done, but possibly concurrently with other threads)
stopall = stoprunning+queue, ///< stop everything
concurrently = 0x010000, ///< run concurrently with already executing code (when whith queued, thread is started when all previously queued threads are done)
keepvars = 0x020000, ///< keep the local variables already set in the context
mainthread = 0x040000, ///< when a thread with this flag set terminates, it also terminates all of its siblings
singlestep = 0x080000, ///< thread must start with pausing mode = singlestep (means: stopping at first statement of a function body, handler or script)
neverpause = 0x100000, ///< thread must never pause, i.e. not inhert domain's defaultpausingmode
implicitreturn = 0x200000, ///< return last evaluation at EOT (like for expressions)
// compilation modifiers
ephemeralSource = 0x400000, ///< threads are kept running and global function+handler definitions are not deleted when originating source code is changed/deleted
anonymousfunction = 0x800000, ///< compile and run as anonymous function body
autorestart = 0x1000000, ///< automatically re-start when scripthost gets uncompiled
};
typedef uint32_t EvaluationFlags;
/// Type info
enum {
// content type flags, usually one per object
typeMask = 0x0FFF,
none = 0x000, ///< no type specification
// - scalars
null = 0x001, ///< NULL/undefined
error = 0x002, ///< Error
numeric = 0x004, ///< numeric value
text = 0x008, ///< text/string value
// - structured
objectvalue = 0x010, ///< is a object with named members (can still have indexed elements, too)
arrayvalue = 0x020, ///< is an array with indexed elements (can still have named fields, too)
// - special
executable = 0x100, ///< executable code
threadref = 0x200, ///< represents a running thread
// type check modifier flags
allof = 0x400, ///< for type requirements only: checked type (within checkedTypesMask) must have all type flags set present in the requirements
nonebut = 0x800, ///< for type requirements only: hecked type (within checkedTypesMask) must not have any type flags set not present in the requirements
checkedTypesMask = typeMask&~(allof|nonebut), ///< type flags minus check flags
// - type classes
alltypes = checkedTypesMask, ///< all types
anyvalid = typeMask-null-error, ///< any type except null and error
scalar = numeric+text, // +json, ///< scalar types (json can also be structured)
structured = objectvalue+arrayvalue, ///< structured types
value = scalar+structured, ///< all value types (excludes executables)
jsonrepresentable = value+null, ///< all data including null (but not error) - this is what is directly representable by JSON
// attributes
attrMask = 0xFFFFF000,
// - for argument checking
optionalarg = null, ///< if set, the argument is optional (means: is is allowed to be null even when null is not explicitly allowed)
multiple = 0x01000, ///< this argument type can occur mutiple times (... signature)
exacttype = 0x02000, ///< if set, type of argument must match, no autoconversion
undefres = 0x04000, ///< if set, and an argument does not match type, the function result is automatically made null/undefined without executing the implementation
async = 0x08000, ///< if set, the object cannot evaluate synchronously
// - storage scopes, attributes and directives for named members
// Note: some of these flags serve more than one of the above categories
lvalue = 0x10000, ///< is (attribute) or should be retrieved as (directive) a left hand value "lvalue"
create = 0x20000, ///< set to create member if not yet existing (directive), special use also for explicitly created errors (attribute)
onlycreate = 0x40000, ///< (directive) set to only create if new, but not overwrite
nooverride = 0x80000, ///< (directive) do not override existing globals by creating a local var
unset = 0x100000, ///< (directive) set to unset/delete member
global = 0x200000, ///< (directive, scope) set to store in global context
threadlocal = 0x400000, ///< (directive, scope) set to store as thread local
builtin = 0x800000, ///< (attribute) set to select only builtin members (functions, values)
objscope = 0x1000000, ///< (scope) set to select only object scope members
classscope = 0x2000000, ///< (scope) set to select only class scope members
// - attribute flag categories
allscopes = global|classscope|objscope|threadlocal, // all flags that can designate member scope
puredirectives = unset|create|onlycreate|nooverride, // all flags that only direct member creation/deletion
nonscopes = puredirectives|lvalue, // all flags that are not member scopes
alldirectives = nonscopes|global, // all flags that can be directives OR scopes/attributes
// - special flags
builtinvalue = 0x4000000, ///< special flag for use in built-in field descriptions (to differentiate from functions)
keeporiginal = 0x8000000, ///< special flag for values that should NOT be replaced by their actualValue()
oneshot = 0x10000000, ///< special flag for values that occur only once, such as event messages. Relevant for triggers, which will auto-reset when oneshot values are involved
freezable = 0x20000000, ///< special flag for values that are delivered as events to trigger evaluation and should be frozen for use in the trigger evaluation, rather than re-read
nowait = 0x40000000, ///< special flags for event sources, indicating that await() should not wait for an event to occur
};
typedef uint32_t TypeInfo;
/// script thread run/debug mode or pause reason
/// @note higher pausing mode include all of the lower ones
typedef enum {
running, ///< run normally, never pause
unpause, ///< unpause, will prevent re-pausing because location is the same etc.
breakpoint, ///< run normally, but pause at breakpoints (breakpoint() in code or by cursor position)
step_out, /// pause at end of user defined functions (aka step out)
step_over, ///< pause at beginning of a statement (aka step over)
step_into, ///< when entering a function, pass "statements" runmode into function's "child thread" (aka step into)
interrupt, ///< externally set interrupt
terminated, ///< as occasion: thread has terminated. As continuing mode -> abort now
numPausingModes
} PausingMode;
/// trigger modes (note: enum values exposed in some API properties, do not change!)
typedef enum {
inactive = 0, ///< trigger is inactive
onGettingTrue = 1, ///< trigger is fired when evaluation result is getting true
onChangingBool = 2, ///< trigger is fired when evaluation result changes boolean value, including getting invalid
onChange = 3, ///< trigger is fired when evaluation result changes (operator== with last result does not return true)
onEvaluation = 4, ///< trigger is fired whenever it gets evaluated
onChangingBoolRisingHoldoffOnly = 5, ///< special mode which applies the holdoff delay only to the rising edge (
} TriggerMode;
/// Argument descriptor
typedef struct {
TypeInfo typeInfo; ///< info about allowed types, checking, open argument lists, etc.
string name; ///< the name of the argument
} ArgumentDescriptor;
// MARK: - EventSink and EventSource
/// evaluation callback
/// @param aEvaluationResult the result of an evaluation
typedef boost::function<void (ScriptObjPtr aEvaluationResult)> EvaluationCB;
/// Event Sink
class EventSink
{
friend class EventSource;
typedef std::set<EventSource *> EventSourceSet;
EventSourceSet mEventSources;
public:
virtual ~EventSink();
/// is called from sources to deliver an event
/// @param aEvent the event object, can be NULL for unspecific events
/// @param aSource the source sending the event
/// @param aRegId the ID passed when registering this event sink with the event source
virtual void processEvent(ScriptObjPtr aEvent, EventSource &aSource, intptr_t aRegId) { /* NOP in base class */ };
/// clear all event sources (unregister from all)
void clearSources();
/// @return number of event sources (senders) this sink currently has
size_t numSources() { return mEventSources.size(); }
/// @return true if sink has any sources
bool hasSources() { return !mEventSources.empty(); }
};
/// event handling callback
typedef boost::function<void (ScriptObjPtr aEvent, EventSource &aSource, intptr_t aRegId)> EventHandlingCB;
/// standalone event handler, delivering events via callback
class EventHandler : public EventSink
{
typedef EventSink inherited;
EventHandlingCB mEventHandlingCB;
public:
/// set handler to call when event arrives
/// @param aEventHandlingCB will be called when handler receives a event from a EventSource
void setHandler(EventHandlingCB aEventHandlingCB);
/// is called from sources to deliver an event
/// @param aEvent the event object, can be NULL for unspecific events
/// @param aSource the source sending the event
/// @param aRegId the ID passed when registering this event sink with the event source
virtual void processEvent(ScriptObjPtr aEvent, EventSource &aSource, intptr_t aRegId) P44_OVERRIDE;
};
class EventFilter : public P44Obj
{
public:
/// check passed aEventObj against this filter, and possibly replace it by a modified/filtered version
/// @param aEventObj reference containing event object, may be replaced by filtered version
/// @return true if object passes filter and must be delivered
virtual bool filteredEventObj(ScriptObjPtr &aEventObj) { return true; } // base class does not actually filter
};
typedef boost::intrusive_ptr<EventFilter> EventFilterPtr;
/// Event Source
class EventSource
{
friend class EventSink;
typedef struct {
intptr_t regId;
EventFilterPtr eventFilter;
} SinkRegistration;
typedef std::map<EventSink *, SinkRegistration> EventSinkMap;
EventSinkMap mEventSinks;
bool mSinksModified;
public:
virtual ~EventSource();
/// send event to all registered event sinks
/// @param aEvent event object, can also be NULL pointer
/// @note the event might get filtered and the event object modified according to filters
/// provided by even sinks when registering
/// @return true if aEvent was delivered (not filtered out) to at least one EvenSink
bool sendEvent(ScriptObjPtr aEvent);
/// register an event sink to get events from this source
/// @param aEventSink the event sink (receiver) to register for events (NULL allowed -> NOP)
/// @param aRegId a registration id private to aEventSink's registration for this event source. This
/// id will be returned with with events via processEvent().
/// @param aFilter an optional EventFilter object that filters events to this registering sink
/// @note registering the same event sink multiple times is allowed, but will not duplicate events sent.
/// Also, the aRegId delivered to a sink will be that specificied in the most recent call to registerForEvents().
void registerForEvents(EventSink* aEventSink, intptr_t aRegId = 0, EventFilterPtr aFilter = nullptr);
void registerForEvents(EventSink& aEventSink, intptr_t aRegId = 0, EventFilterPtr aFilter = nullptr);
/// release an event sink from getting events from this source
/// @param aEventSink the event sink (receiver) to unregister from receiving events (NULL allowed -> NOP)
/// @note tring to unregister an event sink that is not registered is allowed -> NOP
void unregisterFromEvents(EventSink* aEventSink);
void unregisterFromEvents(EventSink& aEventSink);
/// @return number of event sinks (reveivers) this source currently has
size_t numSinks() { return mEventSinks.size(); }
/// @return true if source has any sinks
bool hasSinks() { return !mEventSinks.empty(); }
/// copy all sinks from another source
/// @param aOtherSource the source to copy sinks from (can be NULL -> NOP)
/// @note other source keeps the sinks registered
void copySinksFrom(EventSource* aOtherSource);
};
// MARK: - ScriptObj base class
/// Base Object in scripting
class ScriptObj : public P44LoggingObj
{
typedef P44LoggingObj inherited;
int mAssignmentRefCount; ///< reference count for use of assignmentValue() and deactivateAssignment()
public:
ScriptObj() : mAssignmentRefCount(0) {};
/// @name information
/// @{
/// get type of this value
/// @return get type info
virtual TypeInfo getTypeInfo() const { return null; }; // base object is a null/undefined
/// @return a type description for logs and error messages
static string typeDescription(TypeInfo aInfo, bool aTerse);
/// @return text description for the passed aObj, NULL allowed
static string describe(const ScriptObj* aObj);
static string describe(ScriptObjPtr aObj) { return describe(aObj.get()); }
/// get name
virtual string getIdentifier() const { return ""; };
/// get annotation text - defaults to type description
virtual string getAnnotation() const { return typeDescription(getTypeInfo(), false); };
/// check type compatibility
/// @param aTypeInfo what type(s) we are looking for
/// @return true if this object has any of the types specified in aTypeInfo
bool hasType(TypeInfo aTypeInfo) const { return (getTypeInfo() & aTypeInfo)!=0; }
/// check this object's type compatibility
/// @param aRequirements type requirements (see typeRequirementMet())
/// @return true if this object meets the requirements
bool meetsRequirement(TypeInfo aRequirements) const
{ return typeRequirementMet(getTypeInfo(), aRequirements); }
/// check type compatibility
/// @param aInfo the actual type flags to check
/// @param aRequirements type requirements as follows:
/// - if zero, no type checking at all is performed
/// - otherwise type flags WITHIN checkedTypesMask are checked as follows:
/// - if `allof` bit is set, ALL flags in aRequirements must be set, otherwise
/// it is sufficient that actual type and aRequirements share at least on set bit
/// - if `nonebut` bit is set, NO flag outside those in aRequirements may be set
/// - if aRequirements has other flags within attrMask, one of these must also be present
/// in the actual type (implicit `anyof`)
/// @return true if aInfo meets the requirements
static bool typeRequirementMet(TypeInfo aInfo, TypeInfo aRequirements);
/// check for null/undefined
bool undefined() const { return (getTypeInfo() & null)!=0; }
/// check for defined value (but not error)
bool defined() const { return !undefined() & !isErr(); }
/// check for error
bool isErr() const { return (getTypeInfo() & error)!=0; }
/// logging context to use
virtual P44LoggingObj* loggingContext() const { return NULL; };
/// @return the prefix to be used for logging from this object
virtual string logContextPrefix() P44_OVERRIDE;
/// @return the per-instance log level offset
/// @note is virtual because some objects might want to use the log level offset of another object
virtual int getLogLevelOffset() P44_OVERRIDE;
/// @return associated position, can be NULL
virtual SourceCursor* cursor() { return NULL; }
/// @}
/// @name lazy value loading / proxy objects / lvalue assignment
/// @{
/// @return a pointer to the object's actual value when it is available.
/// Might be false when this object is an lvalue or another type of proxy
/// @note call makeValid() to get a valid version from this object
virtual ScriptObjPtr actualValue() const { return ScriptObjPtr(const_cast<ScriptObj*>(this)); } // simple value objects including this base class have an immediate value
/// Get the actual value of an object (which might be a lvalue or other type of proxy)
/// @param aEvaluationCB will be called with a valid version of the object.
/// @note if called on an already valid object, it returns itself in the callback, so
/// makeValid() can always be called. But for performance reasons, checking valid() before is recommended
virtual void makeValid(EvaluationCB aEvaluationCB);
/// Assign a new value to a lvalue
/// @param aNewValue the value to assign, NULL to remove the lvalue from its container
/// @param aEvaluationCB will be called with a valid version of the object or an error or NULL in case the lvalue was deleted
virtual void assignLValue(EvaluationCB aEvaluationCB, ScriptObjPtr aNewValue);
/// @}
/// @name value getters
/// @{
/// @return the value that should be used to assign to a variable.
/// The purpose of this can be to detach the the assigned value from the original value (e.g. arrays
/// and objects which needs to copy elements/subfields when assiging).
/// @note
/// - Simple values are immutable and can be shared between variables,
/// so this base implementation returns itself.
/// - Some variable types might not be immutable, but work as references, and thus should be
/// assigned as-is. To manage deactivate() for those, calling assignmentValue() increments
/// a reference count so calls to assignmentValue() should be balanced by calls to
/// deactivateAssignment(), which deactivates only when the assignmentValue count gets zero
virtual ScriptObjPtr assignmentValue() const;
/// should be called when assigned variables are
void deactivateAssignment();
/// @return a value to be used in calculations. This should return a basic type whenever possible
virtual ScriptObjPtr calculationValue() { return ScriptObjPtr(this); }
virtual double doubleValue() const { return 0; }; ///< @return a conversion to numeric (using literal syntax), if value is string
virtual bool boolValue() const { return doubleValue()!=0; }; ///< @return a conversion to boolean (true = not numerically 0, not JSON-falsish, not empty string)
virtual string stringValue() const { return getAnnotation(); }; ///< @return a conversion to string of the value
virtual ErrorPtr errorValue() const { return Error::ok(); } ///< @return error value (always an object, OK if not in error)
#if SCRIPTING_JSON_SUPPORT
virtual JsonObjectPtr jsonValue(bool aDescribeNonJSON = false) const; ///< @return a JSON value
static ScriptObjPtr valueFromJSON(JsonObjectPtr aJson); ///< @return ScriptObj representing the JSON passed
#endif
// generic converters
int intValue() const { return (int)doubleValue(); } ///< @return numeric value as int
int64_t int64Value() const { return (int64_t)doubleValue(); } ///< @return numeric value as int64
/// @}
/// @name member access
/// @{
/// get object subfield/member by name
/// @param aName name of the member to find
/// @param aMemberAccessFlags what type and type attributes the returned member must have, defaults to no restriction.
/// If lvalue is set and the member can be created and/or assigned to, an ScriptLvalue might be returned
/// @return ScriptObj representing the member, or NULL if none
/// @note only possibly returns something for container objects marked with "object" type
virtual const ScriptObjPtr memberByName(const string aName, TypeInfo aMemberAccessFlags = none) const { return ScriptObjPtr(); };
/// number of members accessible by index (e.g. positional parameters or array elements)
/// @return number of members
/// @note may or may not overlap with named members of the same object
virtual size_t numIndexedMembers() const { return 0; }
/// get object subfield/member by index, for example positional arguments in a ExecutionContext
/// @param aIndex index of the member to find
/// @param aMemberAccessFlags what type and type attributes the returned member must have.
/// If lvalue is set and the member can be created and/or assigned to, an ScriptLvalue might be returned
/// @return ScriptObj representing the member with index
/// @note only possibly returns something in objects with type attribute "array"
virtual const ScriptObjPtr memberAtIndex(size_t aIndex, TypeInfo aMemberAccessFlags) const { return ScriptObjPtr(); };
/// set new object for named member in this container (and nowhere else!)
/// @param aName name of the member to assign
/// @param aMember the member to assign
/// @return ok or Error describing reason for assignment failure
/// @note this method is primarily an interface for ScriptLvalue and usually should NOT be used directly
/// @note this method must NOT do any scope walks, but apply only to this container. It's the task of
/// memberByName() to find the correct scope and return an lvalue
virtual ErrorPtr setMemberByName(const string aName, const ScriptObjPtr aMember);
/// set new object for member at index (array, positional parameter)
/// @param aIndex name of the member to assign
/// @param aMember the member to assign
/// @param aName optional name of the member (for containers where members have an index AND an name, such as function parameters)
/// @return ok or Error describing reason for assignment failure
virtual ErrorPtr setMemberAtIndex(size_t aIndex, const ScriptObjPtr aMember, const string aName = "");
/// create and initialize a iterator for iterating over this objects members
/// @param aTypeRequirements requirements for the returned objects
/// This filter might not be implemented in all iterators.
/// @return iterator over members of this object
virtual ValueIteratorPtr newIterator(TypeInfo aTypeRequirements) const;
/// @}
/// @name executable support
/// @{
/// get information (typeInfo and possibly a name) for a positional argument
/// @param aIndex the argument index (0..N)
/// @param aArgDesc where to store the descriptor
/// @return true if aArgDesc was set, false if no argument exists at this index
/// @note functions might have an open argument list, so do not try to exhaust this
virtual bool argumentInfo(size_t aIndex, ArgumentDescriptor& aArgDesc) const { return false; };
/// get context to call this object as a (sub)routine of a given context
/// @param aMainContext the context from where to call from (evaluate in) this implementation
/// For executing script body code, aMainContext is always the domain (but can be passed NULL as script bodies
/// should know their domain already (if passed, it will be checked for consistency)
/// @param aThread the thread this call will originate from, e.g. when requesting context for a function call.
/// NULL means that code is not started from a running thread, such as scripts, handlers, triggers.
/// @note the context might be pre-existing (in case of scriptbody code which gets associated with the context it
/// is hosted in) or created new (in case of functions which run in a temporary private context)
/// @return new context suitable for evaluating this implementation, NULL if none
virtual ExecutionContextPtr contextForCallingFrom(ScriptMainContextPtr aMainContext, ScriptCodeThreadPtr aThread) const { return ExecutionContextPtr(); }
/// @return true if this object originates from the specified source
/// @note this is needed to remove objects such as functions and handlers when their source changes or is deleted
virtual bool originatesFrom(SourceContainerPtr aSource) const { return false; }
/// make ready for deletion, break links that might be parts of retain loops
/// @note this is used before freeing a object which originatesFrom() a given source and generally
/// before trying to delete objects.
virtual void deactivate() {};
/// @return true if this object's definition/declaration is floating, i.e. when it carries its own source code
/// that is no longer connected with the originating source code (and thus can't get removed/replaced by
/// changing source code)
virtual bool floating() const { return false; }
/// @}
/// @name operators
/// @{
// boolean, always returning native C++ boolean
// - generic
bool operator!() const;
bool operator&&(const ScriptObj& aRightSide) const;
bool operator||(const ScriptObj& aRightSide) const;
// - derived
bool operator!=(const ScriptObj& aRightSide) const;
bool operator>=(const ScriptObj& aRightSide) const;
bool operator>(const ScriptObj& aRightSide) const;
bool operator<=(const ScriptObj& aRightSide) const;
// - virtual, type-specific
virtual bool operator<(const ScriptObj& aRightSide) const;
virtual bool operator==(const ScriptObj& aRightSide) const;
// arithmetic, returning a ScriptObjPtr, type-specific
virtual ScriptObjPtr operator+(const ScriptObj& aRightSide) const { return new ScriptObj(); };
virtual ScriptObjPtr operator-(const ScriptObj& aRightSide) const { return new ScriptObj(); };
virtual ScriptObjPtr operator*(const ScriptObj& aRightSide) const { return new ScriptObj(); };
virtual ScriptObjPtr operator/(const ScriptObj& aRightSide) const { return new ScriptObj(); };
virtual ScriptObjPtr operator%(const ScriptObj& aRightSide) const { return new ScriptObj(); };
/// @}
/// @name event handling / triggering support
/// @{
/// @return true if this object represents (or is itself) an event source
virtual bool isEventSource() const { return false; }; // base object does not represent a event source
/// register an event sink with the event source linked to, and possibly to be filtered by, this ScriptObj
/// @param aEventSink the event sink that wants to receive the (possibly filtered) events
/// @note this wrapper allows subclasses link and filter to the source in a way specified by additional
/// data carried in the (specialized event source placeholder) object.
virtual void registerForFilteredEvents(EventSink* aEventSink, intptr_t aRegId = 0) { }; // NOP in base class, does not link to an event source
/// pass my sinks to a replacement source
/// @note this is NOP unless this object is a event source placeholder (uninitialized global variable)
virtual void passSinksToReplacementSource(ScriptObjPtr aReplacementSource) { }; // NOP everywhere except for event placeholders
/// @}
};
// MARK: - lvalues
/// Base class for a value reference that might be assigned to
class ScriptLValue : public ScriptObj
{
typedef ScriptObj inherited;
protected:
ScriptObjPtr mCurrentValue;
ScriptLValue(ScriptObjPtr aCurrentValue) : mCurrentValue(aCurrentValue) {};
virtual ~ScriptLValue() { deactivate(); } // even if deactivate() is usually called before dtor, make sure it happens even if not
public:
virtual TypeInfo getTypeInfo() const P44_OVERRIDE { return lvalue; };
virtual string getAnnotation() const P44_OVERRIDE { return "lvalue"; };
virtual void deactivate() P44_OVERRIDE { mCurrentValue.reset(); inherited::deactivate(); }
/// @return true when the object's value is available. Might be false when this object is an lvalue or another type of proxy
/// @note call makeValid() to get a valid version from this object
virtual ScriptObjPtr actualValue() const P44_OVERRIDE { return mCurrentValue; } // LValues are valid if they have a current value
/// @return a value to be used in calculations. This should return a basic type whenever possible
virtual ScriptObjPtr calculationValue() P44_OVERRIDE { return mCurrentValue; }
/// Get the actual value of an object (which might be a lvalue or other type of proxy)
/// @param aEvaluationCB will be called with a valid version of the object.
/// @note if called on an already valid object, it returns itself in the callback, so
/// makeValid() can always be called. But for performance reasons, checking valid() before is recommended
virtual void makeValid(EvaluationCB aEvaluationCB) P44_OVERRIDE;
/// Assign a new value to a lvalue
/// @param aNewValue the value to assign, NULL to remove the lvalue from its container
/// @param aEvaluationCB will be called with a valid version of the object or an error or NULL in case the lvalue was deleted
virtual void assignLValue(EvaluationCB aEvaluationCB, ScriptObjPtr aNewValue) P44_OVERRIDE = 0;
};
typedef boost::intrusive_ptr<ScriptLValue> ScriptLValuePtr;
class StandardLValue : public ScriptLValue
{
typedef ScriptLValue inherited;
string mMemberName;
size_t mMemberIndex;
ScriptObjPtr mContainer;
public:
/// create a lvalue for a container which has setMemberByName()
/// @note this should be called by suitable container's memberByName() only
/// @note subclasses might provide optimized/different mechanisms
StandardLValue(ScriptObjPtr aContainer, const string aMemberName, ScriptObjPtr aCurrentValue);
/// create a lvalue for a container which has setMemberAtIndex()
StandardLValue(ScriptObjPtr aContainer, size_t aMemberIndex, ScriptObjPtr aCurrentValue);
virtual ~StandardLValue() { deactivate(); } // even if deactivate() is usually called before dtor, make sure it happens even if not
virtual void deactivate() P44_OVERRIDE { mContainer.reset(); inherited::deactivate(); }
/// Assign a new value to a lvalue
/// @param aNewValue the value to assign, NULL to remove the lvalue from its container
/// @param aEvaluationCB will be called with a valid version of the object or an error or NULL in case the lvalue was deleted
virtual void assignLValue(EvaluationCB aEvaluationCB, ScriptObjPtr aNewValue) P44_OVERRIDE;
/// get identifier (name) of this lvalue object
virtual string getIdentifier() const P44_OVERRIDE { return mMemberName; };
};
// MARK: - iterators
class ValueIterator : public P44Obj
{
public:
/// reset iterator to its initial state (beginning of the iterating sequence)
/// @note implementation might just flag internal state for resetting, actually doing it at obtainValue()/obtainKey()
virtual void reset() = 0;
/// advance the iterator to the next item
/// @note implementation might just flag internal state for incrementing, actually doing it at obtainValue()/obtainKey()
virtual void next() = 0;
/// Get the current value
/// @param aMemberAccessFlags what type and type attributes the returned member must have.
/// If lvalue is set and the current value can be assigned to, an ScriptLvalue might be returned
/// The current value might be a placeholder value and might need makeValid() to ensure the actual value is retrieved
/// @return Value or null if the iterator is exhausted. Null elements must return an explicit null
virtual ScriptObjPtr obtainValue(TypeInfo aMemberAccessFlags) = 0;
/// Get the current key
/// @param aNumericPreferred if set, key is returned as number/index if possible for the container
/// @return key (string or integer) or null if the iterator is exhaused
virtual ScriptObjPtr obtainKey(bool aNumericPreferred) = 0;
};
/// iterator using memberAtIndex() and numIndexedMembers()
class IndexedValueIterator : public ValueIterator
{
typedef ValueIterator inherited;
protected:
size_t mCurrentIndex;
ScriptObjPtr mIteratedObj;
/// @return true if internal index is in range of the indexable values
bool validIndex();
public:
/// iterator over a regular ScriptObj
/// @param aObj the object that will be iterated over
IndexedValueIterator(const ScriptObj* aObj);
virtual void reset() P44_OVERRIDE;
virtual void next() P44_OVERRIDE;
virtual ScriptObjPtr obtainKey(bool aNumericPreferred) P44_OVERRIDE;
virtual ScriptObjPtr obtainValue(TypeInfo aMemberAccessFlags) P44_OVERRIDE;
};
// MARK: - Special NULL values
/// an explicitly annotated null value (in contrast to ScriptObj base class which is a non-annotated null)
class AnnotatedNullValue : public ScriptObj
{
typedef ScriptObj inherited;
string mAnnotation;
public:
AnnotatedNullValue(string aAnnotation) : mAnnotation(aAnnotation) {};
virtual string getAnnotation() const P44_OVERRIDE { return mAnnotation; }; // specific annotation...
virtual string stringValue() const P44_OVERRIDE { return inherited::getAnnotation(); }; // ...but stringValue must return the default annotation!
};
/// a NULL value that event sinks can register to, so in case it is replaced (i.e. as a global variable) it can
/// pass over the registered sinks to the replacing object
class EventPlaceholderNullValue P44_FINAL : public AnnotatedNullValue, public EventSource
{
typedef AnnotatedNullValue inherited;
public:
EventPlaceholderNullValue(string aAnnotation);
virtual bool isEventSource() const P44_OVERRIDE { return true; } // is an event source
virtual void registerForFilteredEvents(EventSink* aEventSink, intptr_t aRegId = 0) P44_OVERRIDE;
virtual void passSinksToReplacementSource(ScriptObjPtr aReplacementSource) P44_OVERRIDE;
};
/// a NULL value which represents a one-shot event source. The actual value only exists
/// when an event occurs, and is delivered to the event sink, which then freezes it
/// for trigger expression evaluation.
/// Optionally, the placeholder can also hold a filter
class OneShotEventNullValue : public AnnotatedNullValue
{
typedef AnnotatedNullValue inherited;
EventSource *mEventSource;
EventFilterPtr mFilter;
protected:
virtual EventFilterPtr eventFilter() { return mFilter; }
public:
OneShotEventNullValue(EventSource *aEventSource, string aAnnotation = "no event now", EventFilterPtr aFilter = nullptr);
virtual void deactivate() P44_OVERRIDE { mFilter.reset(); }
virtual TypeInfo getTypeInfo() const P44_OVERRIDE { return null|oneshot|freezable|keeporiginal; }; ///< when not delivered as event, the value is always NULL. When delivered as event, it is to be kept as-is!
virtual bool isEventSource() const P44_OVERRIDE;
virtual void registerForFilteredEvents(EventSink* aEventSink, intptr_t aRegId = 0) P44_OVERRIDE;
};
// MARK: - Error Values
/// An error value
class ErrorValue : public ScriptObj
{
typedef ScriptObj inherited;
protected:
ErrorPtr mErr;
bool mCaught;
public:
ErrorValue(ErrorPtr aError) : mErr(aError), mCaught(false) {};
ErrorValue(ScriptError::ErrorCodes aErrCode, const char *aFmt, ...);
ErrorValue(ScriptObjPtr aErrVal);
static ScriptObjPtr trueOrError(ErrorPtr aError); ///< return a ErrorValue if aError is set and not OK, a true value otherwise
static ScriptObjPtr nothingOrError(ErrorPtr aError); ///< return a ErrorValue if aError is set and not OK, nothing otherwise
virtual string getAnnotation() const P44_OVERRIDE { return "error"; };
virtual TypeInfo getTypeInfo() const P44_OVERRIDE { return error; };
// value getters
virtual double doubleValue() const P44_OVERRIDE { return mErr ? 0 : mErr->getErrorCode(); };
virtual string stringValue() const P44_OVERRIDE { return Error::text(mErr); };
virtual ErrorPtr errorValue() const P44_OVERRIDE { return mErr ? mErr : Error::ok(); };
#if SCRIPTING_JSON_SUPPORT
virtual JsonObjectPtr jsonValue(bool aDescribeNonJSON = false) const P44_OVERRIDE;
#endif
bool caught() { return mCaught; } ///< @return true if error was caught (must not be thrown any more)
void setCaught(bool aCaught) { mCaught = aCaught; } ///< set "caught" state
// operators
virtual bool operator==(const ScriptObj& aRightSide) const P44_OVERRIDE;
};
// MARK: - ThreadValue
class ThreadValue : public ScriptObj, public EventSink
{
typedef ScriptObj inherited;
ScriptCodeThreadPtr mThread;
ScriptObjPtr mThreadExitValue;
public:
ThreadValue(ScriptCodeThreadPtr aThread);
virtual ~ThreadValue() { deactivate(); } // even if deactivate() is usually called before dtor, make sure it happens even if not
virtual string getAnnotation() const P44_OVERRIDE { return "thread"; };
virtual TypeInfo getTypeInfo() const P44_OVERRIDE;
virtual void deactivate() P44_OVERRIDE;
virtual ScriptObjPtr calculationValue() P44_OVERRIDE; ///< ThreadValue calculates to NULL as long as running or to the thread's exit value
virtual bool isEventSource() const P44_OVERRIDE;
virtual void registerForFilteredEvents(EventSink* aEventSink, intptr_t aRegId = 0) P44_OVERRIDE;
ScriptCodeThreadPtr thread() { return mThread; }; ///< @return the thread
virtual void processEvent(ScriptObjPtr aEvent, EventSource &aSource, intptr_t aRegId) P44_OVERRIDE;
};
// MARK: - Regular value classes
class NumericValue : public ScriptObj
{
typedef ScriptObj inherited;
protected:
double mNum;
public:
NumericValue(double aNumber) : mNum(aNumber) {};
NumericValue(bool aBool) : mNum(aBool ? 1 : 0) {};
NumericValue(int aInt) : mNum(aInt) {};
NumericValue(int64_t aInt) : mNum(aInt) {};
NumericValue(size_t aInt) : mNum(aInt) {};
virtual string getAnnotation() const P44_OVERRIDE { return "numeric"; };
virtual TypeInfo getTypeInfo() const P44_OVERRIDE { return numeric; };
// value getters
virtual double doubleValue() const P44_OVERRIDE { return mNum; }; // native
virtual string stringValue() const P44_OVERRIDE { return string_format("%lg", doubleValue()); };
#if SCRIPTING_JSON_SUPPORT
virtual JsonObjectPtr jsonValue(bool aDescribeNonJSON = false) const P44_OVERRIDE { return JsonObject::newDouble(doubleValue()); };
#endif
// operators
virtual bool operator<(const ScriptObj& aRightSide) const P44_OVERRIDE;
virtual bool operator==(const ScriptObj& aRightSide) const P44_OVERRIDE;
virtual ScriptObjPtr operator+(const ScriptObj& aRightSide) const P44_OVERRIDE;
virtual ScriptObjPtr operator-(const ScriptObj& aRightSide) const P44_OVERRIDE;
virtual ScriptObjPtr operator*(const ScriptObj& aRightSide) const P44_OVERRIDE;
virtual ScriptObjPtr operator/(const ScriptObj& aRightSide) const P44_OVERRIDE;
virtual ScriptObjPtr operator%(const ScriptObj& aRightSide) const P44_OVERRIDE;
};
/// BoolValue is a NumericValue that can only be initialized to 0 or 1 via ctor and
/// when asked about its jsonValue(), actually returns a JSON bool value
class BoolValue : public NumericValue
{
typedef NumericValue inherited;
public:
BoolValue(bool aBool) : inherited(aBool) {};
virtual string getAnnotation() const P44_OVERRIDE { return "boolean"; };
virtual string stringValue() const P44_OVERRIDE { return boolValue() ? "true" : "false"; };
// value getters
#if SCRIPTING_JSON_SUPPORT
virtual JsonObjectPtr jsonValue(bool aDescribeNonJSON = false) const P44_OVERRIDE { return JsonObject::newBool(boolValue()); };
#endif
};
/// IntegerValue is a NumericValue that returns a JSON integer value
/// when asked about its jsonValue()
class IntegerValue : public NumericValue
{
typedef NumericValue inherited;
public:
IntegerValue(int64_t aInt) : inherited(aInt) {};
virtual string getAnnotation() const P44_OVERRIDE { return "integer"; };