-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathslim_eidos_block.cpp
2107 lines (1714 loc) · 87.9 KB
/
slim_eidos_block.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
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
//
// slim_script_block.cpp
// SLiM
//
// Created by Ben Haller on 6/7/15.
// Copyright (c) 2015-2025 Philipp Messer. All rights reserved.
// A product of the Messer Lab, http://messerlab.org/slim/
//
#include "slim_eidos_block.h"
#include "eidos_interpreter.h"
#include "slim_globals.h"
#include "eidos_call_signature.h"
#include "eidos_property_signature.h"
#include "eidos_ast_node.h"
#include "community.h"
#include "species.h"
#include "interaction_type.h"
#include "subpopulation.h"
#include "errno.h"
#include "string.h"
#include <string>
#include <algorithm>
#include <vector>
std::ostream& operator<<(std::ostream& p_out, SLiMEidosBlockType p_block_type)
{
switch (p_block_type)
{
case SLiMEidosBlockType::SLiMEidosEventFirst: p_out << "first()"; break;
case SLiMEidosBlockType::SLiMEidosEventEarly: p_out << "early()"; break;
case SLiMEidosBlockType::SLiMEidosEventLate: p_out << "late()"; break;
case SLiMEidosBlockType::SLiMEidosInitializeCallback: p_out << "initialize()"; break;
case SLiMEidosBlockType::SLiMEidosMutationEffectCallback: p_out << "mutationEffect()"; break;
case SLiMEidosBlockType::SLiMEidosFitnessEffectCallback: p_out << "fitnessEffect()"; break;
case SLiMEidosBlockType::SLiMEidosInteractionCallback: p_out << "interaction()"; break;
case SLiMEidosBlockType::SLiMEidosMateChoiceCallback: p_out << "mateChoice()"; break;
case SLiMEidosBlockType::SLiMEidosModifyChildCallback: p_out << "modifyChild()"; break;
case SLiMEidosBlockType::SLiMEidosRecombinationCallback: p_out << "recombination()"; break;
case SLiMEidosBlockType::SLiMEidosMutationCallback: p_out << "mutation()"; break;
case SLiMEidosBlockType::SLiMEidosSurvivalCallback: p_out << "survival()"; break;
case SLiMEidosBlockType::SLiMEidosReproductionCallback: p_out << "reproduction()"; break;
case SLiMEidosBlockType::SLiMEidosUserDefinedFunction: p_out << "function"; break;
case SLiMEidosBlockType::SLiMEidosNoBlockType: p_out << "NO BLOCK"; break;
}
return p_out;
}
static inline bool SLiM_TokenIsCallbackIdentifier(EidosToken *token)
{
if (token->token_type_ != EidosTokenType::kTokenIdentifier)
return false;
if ((token->token_string_.compare(gStr_first) == 0) ||
(token->token_string_.compare(gStr_early) == 0) ||
(token->token_string_.compare(gStr_late) == 0) ||
(token->token_string_.compare(gStr_initialize) == 0) ||
(token->token_string_.compare(gStr_fitnessEffect) == 0) ||
(token->token_string_.compare(gStr_mutationEffect) == 0) ||
(token->token_string_.compare(gStr_mutation) == 0) ||
(token->token_string_.compare(gStr_interaction) == 0) ||
(token->token_string_.compare(gStr_mateChoice) == 0) ||
(token->token_string_.compare(gStr_modifyChild) == 0) ||
(token->token_string_.compare(gStr_recombination) == 0) ||
(token->token_string_.compare(gStr_survival) == 0) ||
(token->token_string_.compare(gStr_reproduction) == 0))
return true;
return false;
}
//
// SLiMEidosScript
//
#pragma mark -
#pragma mark SLiMEidosScript
#pragma mark -
SLiMEidosScript::SLiMEidosScript(const std::string &p_script_string) : EidosScript(p_script_string, nullptr, 0, 0, 0)
{
}
SLiMEidosScript::~SLiMEidosScript(void)
{
}
EidosASTNode *SLiMEidosScript::Parse_SLiMFile(void)
{
EidosToken *virtual_token = new EidosToken(EidosTokenType::kTokenContextFile, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(virtual_token, true);
try
{
// We handle the grammar a bit differently than how it is printed in the railroad diagrams in the doc.
// Parsing of the optional tick range is done in Parse_SLiMEidosBlock() since it ends up as children of that node.
while (current_token_type_ != EidosTokenType::kTokenEOF)
{
// For multispecies, we now look at the current token and handle it specially if it is "species" or "ticks".
if ((current_token_type_ == EidosTokenType::kTokenIdentifier) && (current_token_->token_string_.compare(gStr_species) == 0))
node->AddChild(Parse_SpeciesSpecifier());
else if ((current_token_type_ == EidosTokenType::kTokenIdentifier) && (current_token_->token_string_.compare(gStr_ticks) == 0))
node->AddChild(Parse_TicksSpecifier());
else
node->AddChild(Parse_SLiMEidosBlock());
}
Match(EidosTokenType::kTokenEOF, "SLiM file");
}
catch (...)
{
// destroy the parse root and return it to the pool; the tree must be allocated out of gEidosASTNodePool!
if (node)
{
node->~EidosASTNode();
gEidosASTNodePool->DisposeChunk(const_cast<EidosASTNode*>(node));
}
throw;
}
return node;
}
EidosASTNode *SLiMEidosScript::Parse_SpeciesSpecifier(void)
{
EidosASTNode *node = nullptr, *species_name;
try {
// This parses "species identifier" specifiers, creating a node with the species name as its child
node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
Match(EidosTokenType::kTokenIdentifier, "species specifier");
species_name = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
node->AddChild(species_name);
#if (SLIMPROFILING == 1)
// PROFILING
node->full_range_end_token_ = current_token_;
#endif
Match(EidosTokenType::kTokenIdentifier, "species specifier");
}
catch (...)
{
if (node)
{
node->~EidosASTNode();
gEidosASTNodePool->DisposeChunk(const_cast<EidosASTNode*>(node));
}
throw;
}
return node;
}
EidosASTNode *SLiMEidosScript::Parse_TicksSpecifier(void)
{
EidosASTNode *node = nullptr, *species_name;
try {
// This parses "ticks identifier" specifiers, creating a node with the species name as its child
node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
Match(EidosTokenType::kTokenIdentifier, "ticks specifier");
species_name = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
node->AddChild(species_name);
#if (SLIMPROFILING == 1)
// PROFILING
node->full_range_end_token_ = current_token_;
#endif
Match(EidosTokenType::kTokenIdentifier, "ticks specifier");
}
catch (...)
{
if (node)
{
node->~EidosASTNode();
gEidosASTNodePool->DisposeChunk(const_cast<EidosASTNode*>(node));
}
throw;
}
return node;
}
EidosASTNode *SLiMEidosScript::Parse_SLiMEidosBlock(void)
{
EidosToken *virtual_token = new EidosToken(EidosTokenType::kTokenContextEidosBlock, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *slim_script_block_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(virtual_token, true);
// We handle the grammar a bit differently than how it is printed in the railroad diagrams in the doc.
// We parse the slim_script_info section here, as part of the script block.
try
{
// Keep track of the beginning of the script block, to patch virtual_token below...
const int32_t token_start = current_token_->token_start_;
const int32_t token_UTF16_start = current_token_->token_UTF16_start_;
const int32_t token_line = current_token_->token_line_; // we use the line of our starting token
EidosASTNode *compound_statement_node = nullptr;
if (current_token_type_ == EidosTokenType::kTokenFunction)
{
// Starting in SLiM 2.5, the user can declare their own functions at the top level in the SLiM file.
// Since the SLiM input file is not an Eidos interpreter block, we have to handle that ourselves.
EidosASTNode *function_node = Parse_FunctionDecl();
if (function_node->children_.size() == 4)
{
compound_statement_node = function_node->children_[3]; // for the virtual token range below
slim_script_block_node->AddChild(function_node);
}
}
else
{
// The first element is an optional script identifier like s1; we check here that an identifier matches the
// pattern sX before eating it, since an identifier here could also be a callback tag like "mutationEffect".
if ((current_token_type_ == EidosTokenType::kTokenIdentifier) && SLiMEidosScript::StringIsIDWithPrefix(current_token_->token_string_, 's'))
{
// a script identifier like s1 is present; add it
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM script block");
}
// Next comes an optional tick X, or a tick range X:Y, X:, or :Y (a lone : is not legal).
// We don't parse this as if the : were an operator, since we have to allow for a missing start or end;
// for this reason, we make the : into a node of its own, with no children, so X:Y, X:, and :Y are distinct.
// SLiMEidosBlock::SLiMEidosBlock(EidosASTNode *p_root_node) handles this anomalous tree structure.
//
// BCH 3/6/2024: We now allow ticks specifiers with a complex structure such as N, N+10, N:(N+10),
// (N+10):(N+20), and even things like 1:(L ? N+10 else N+20), etc. Note that parentheses are needed
// for the sequence expressions that involve complicated operands. This is tricky. As just mentioned,
// we want to allow X: and :Y, so this is not a normal Eidos sequence-expression, and we can't parse it
// as such. We use special _NOSEQ parsing methods below to parse just X, the colon, and Y, without
// allowing the colon to become a sequence-expression. That means that "N+10:N+20" would parse as "N+10",
// the colon, and "N+20", which might seem nice, and might even be what the user wants -- but it is
// not how the operator precedence of the : operator normally works in Eidos, which would be N+(10:N)+20
// instead. To avoid confusion over this conflict, we require that if X and Y are both present (X:Y),
// they must both be "primary" or "postfix" expressions -- a number, an identifier, a parenthesized expression,
// function and method calls, etc., where the top-level operator node is higher precedence than the
// sequence operator. This way, X:Y always behaves as it does in Eidos, even though we are parsing it
// in a different way, because we disallow all of the conflicting cases. That check is done at evaluation
// time; see EvaluateScriptBlockTickRanges(). Here we just do the parsing, using NOSEQ.
if (current_token_type_ == EidosTokenType::kTokenColon)
{
// The tick range starts with a colon; first eat that
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenColon, "SLiM script block");
// In this situation, we must have an end tick subexpression; a lone colon is not a legal tick specifier
if (SLiM_TokenIsCallbackIdentifier(current_token_))
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; expected an integer expression for the tick range end." << EidosTerminate(current_token_);
// BCH 3/6/2024: We used to require a number here:
//
//if (current_token_type_ == EidosTokenType::kTokenNumber)
// slim_script_block_node->AddChild(Parse_Constant());
//
// but now we allow any NOSEQ subexpression:
try
{
EidosASTNode *end_tick_expr = nullptr;
end_tick_expr = Parse_ConditionalExpr_NOSEQ();
slim_script_block_node->AddChild(end_tick_expr);
}
catch (...)
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; expected an integer expression for the tick range end." << EidosTerminate(current_token_);
// Introduce a bad node, since we're being error-tolerant
slim_script_block_node->AddChild(Parse_Constant());
}
}
else if (!SLiM_TokenIsCallbackIdentifier(current_token_))
{
// A start tick subexpression appears to be present; add it
// We used to require a number here:
//
//if (current_token_type_ == EidosTokenType::kTokenNumber)
// slim_script_block_node->AddChild(Parse_Constant());
//
// but now we allow any NOSEQ subexpression:
try
{
EidosASTNode *start_tick_expr = nullptr;
start_tick_expr = Parse_ConditionalExpr_NOSEQ();
slim_script_block_node->AddChild(start_tick_expr);
}
catch (...)
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; expected an integer expression for the tick range start." << EidosTerminate(current_token_);
// Introduce a bad node, since we're being error-tolerant
slim_script_block_node->AddChild(Parse_Constant());
}
// If a colon is present, we have a range, although it could be just X:
if (current_token_type_ == EidosTokenType::kTokenColon)
{
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenColon, "SLiM script block");
// If an end tick subexpression is present, add it
if (!SLiM_TokenIsCallbackIdentifier(current_token_))
{
// We used to require a number here:
//
//if (current_token_type_ == EidosTokenType::kTokenNumber)
// slim_script_block_node->AddChild(Parse_Constant());
//
// but now we allow any NOSEQ subexpression:
try
{
EidosASTNode *end_tick_expr = nullptr;
end_tick_expr = Parse_ConditionalExpr_NOSEQ();
slim_script_block_node->AddChild(end_tick_expr);
}
catch (...)
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; expected an integer expression for the tick range end." << EidosTerminate(current_token_);
// Introduce a bad node, since we're being error-tolerant
slim_script_block_node->AddChild(Parse_Constant());
}
}
}
}
// Now we are to the point of parsing the actual slim_script_block
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
if (current_token_->token_string_.compare(gStr_first) == 0)
{
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM first() event");
Match(EidosTokenType::kTokenLParen, "SLiM first() event");
Match(EidosTokenType::kTokenRParen, "SLiM first() event");
}
else if (current_token_->token_string_.compare(gStr_early) == 0)
{
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM early() event");
Match(EidosTokenType::kTokenLParen, "SLiM early() event");
Match(EidosTokenType::kTokenRParen, "SLiM early() event");
}
else if (current_token_->token_string_.compare(gStr_late) == 0)
{
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM late() event");
Match(EidosTokenType::kTokenLParen, "SLiM late() event");
Match(EidosTokenType::kTokenRParen, "SLiM late() event");
}
else if (current_token_->token_string_.compare(gStr_initialize) == 0)
{
slim_script_block_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM initialize() callback");
Match(EidosTokenType::kTokenLParen, "SLiM initialize() callback");
Match(EidosTokenType::kTokenRParen, "SLiM initialize() callback");
}
else if (current_token_->token_string_.compare(gStr_fitnessEffect) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM fitnessEffect() callback");
Match(EidosTokenType::kTokenLParen, "SLiM fitnessEffect() callback");
// A (optional) subpopulation id is present; add it
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM fitnessEffect() callback");
}
Match(EidosTokenType::kTokenRParen, "SLiM fitnessEffect() callback");
}
else if (current_token_->token_string_.compare(gStr_mutationEffect) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM mutationEffect() callback");
Match(EidosTokenType::kTokenLParen, "SLiM mutationEffect() callback");
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
// A (required) mutation type id is present; add it
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM mutationEffect() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; a mutation type id is required in mutationEffect() callback definitions." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
if (current_token_type_ == EidosTokenType::kTokenComma)
{
// A (optional) subpopulation id is present; add it
Match(EidosTokenType::kTokenComma, "SLiM mutationEffect() callback");
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM mutationEffect() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; subpopulation id expected." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
}
Match(EidosTokenType::kTokenRParen, "SLiM mutationEffect() callback");
}
else if (current_token_->token_string_.compare(gStr_mutation) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM mutation() callback");
Match(EidosTokenType::kTokenLParen, "SLiM mutation() callback");
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
// A (optional) mutation type id (or NULL) is present; add it
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM mutation() callback");
if (current_token_type_ == EidosTokenType::kTokenComma)
{
// A (optional) subpopulation id is present; add it
Match(EidosTokenType::kTokenComma, "SLiM mutation() callback");
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM mutation() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; subpopulation id expected." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
}
}
Match(EidosTokenType::kTokenRParen, "SLiM mutation() callback");
}
else if (current_token_->token_string_.compare(gStr_interaction) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM interaction() callback");
Match(EidosTokenType::kTokenLParen, "SLiM interaction() callback");
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
// A (required) interaction type id is present; add it
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM interaction() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; an interaction type id is required in interaction() callback definitions." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
if (current_token_type_ == EidosTokenType::kTokenComma)
{
// A (optional) subpopulation id is present; add it
Match(EidosTokenType::kTokenComma, "SLiM interaction() callback");
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM interaction() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; subpopulation id expected." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
}
Match(EidosTokenType::kTokenRParen, "SLiM interaction() callback");
}
else if (current_token_->token_string_.compare(gStr_mateChoice) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM mateChoice() callback");
Match(EidosTokenType::kTokenLParen, "SLiM mateChoice() callback");
// A (optional) subpopulation id is present; add it
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM mateChoice() callback");
}
Match(EidosTokenType::kTokenRParen, "SLiM mateChoice() callback");
}
else if (current_token_->token_string_.compare(gStr_modifyChild) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM modifyChild() callback");
Match(EidosTokenType::kTokenLParen, "SLiM modifyChild() callback");
// A (optional) subpopulation id is present; add it
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM modifyChild() callback");
}
Match(EidosTokenType::kTokenRParen, "SLiM modifyChild() callback");
}
else if (current_token_->token_string_.compare(gStr_recombination) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM recombination() callback");
Match(EidosTokenType::kTokenLParen, "SLiM recombination() callback");
// A (optional) subpopulation id (or NULL) is present; add it
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM recombination() callback");
if (current_token_type_ == EidosTokenType::kTokenComma)
{
// A (optional) chromosome identifier (id or symbol, or NULL) is present; add it
Match(EidosTokenType::kTokenComma, "SLiM recombination() callback");
if (current_token_type_ == EidosTokenType::kTokenString)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenString, "SLiM recombination() callback");
}
else if (current_token_type_ == EidosTokenType::kTokenNumber)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenNumber, "SLiM recombination() callback");
}
else if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM reproduction() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; chromosome-id (integer or string) or NULL expected." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
}
}
Match(EidosTokenType::kTokenRParen, "SLiM recombination() callback");
}
else if (current_token_->token_string_.compare(gStr_survival) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM survival() callback");
Match(EidosTokenType::kTokenLParen, "SLiM survival() callback");
// A (optional) subpopulation id is present; add it
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM survival() callback");
}
Match(EidosTokenType::kTokenRParen, "SLiM survival() callback");
}
else if (current_token_->token_string_.compare(gStr_reproduction) == 0)
{
EidosASTNode *callback_info_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_);
slim_script_block_node->AddChild(callback_info_node);
Match(EidosTokenType::kTokenIdentifier, "SLiM reproduction() callback");
Match(EidosTokenType::kTokenLParen, "SLiM reproduction() callback");
// A (optional) subpopulation id (or NULL) is present; add it
if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM reproduction() callback");
if (current_token_type_ == EidosTokenType::kTokenComma)
{
// A (optional) sex string (or NULL) is present; add it
Match(EidosTokenType::kTokenComma, "SLiM reproduction() callback");
if (current_token_type_ == EidosTokenType::kTokenString)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenString, "SLiM reproduction() callback");
}
else if (current_token_type_ == EidosTokenType::kTokenIdentifier)
{
callback_info_node->AddChild(new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(current_token_));
Match(EidosTokenType::kTokenIdentifier, "SLiM reproduction() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; sex of 'M' or 'F' expected." << EidosTerminate(current_token_);
// Make a placeholder bad node, to be error-tolerant
EidosToken *bad_token = new EidosToken(EidosTokenType::kTokenBad, gEidosStr_empty_string, 0, 0, 0, 0, -1);
EidosASTNode *bad_node = new (gEidosASTNodePool->AllocateChunk()) EidosASTNode(bad_token, true);
callback_info_node->AddChild(bad_node);
}
}
}
Match(EidosTokenType::kTokenRParen, "SLiM reproduction() callback");
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected identifier " << *current_token_ << "; expected an event declaration (first, early, late), a callback declaration (initialize, fitnessEffect, interaction, mateChoice, modifyChild, mutation, mutationEffect, recombination, reproduction, or survival), or a function declaration." << EidosTerminate(current_token_);
// Consume the stray identifier, to be error-tolerant
Consume();
}
}
else
{
if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): unexpected token " << *current_token_ << "; expected an event declaration (first, early, late), a callback declaration (initialize, fitnessEffect, interaction, mateChoice, modifyChild, mutation, mutationEffect, recombination, reproduction, or survival), or a function declaration. Note that early() is no longer a default script block type that may be omitted; it must now be specified explicitly." << EidosTerminate(current_token_);
// Consume the stray identifier, to be error-tolerant
Consume();
}
// Regardless of what happened above, all Eidos blocks end with a compound statement, which is the last child of the node
compound_statement_node = Parse_CompoundStatement();
slim_script_block_node->AddChild(compound_statement_node);
}
// Patch virtual_token to contain the range from beginning to end of the script block
if (compound_statement_node)
{
const int32_t token_end = compound_statement_node->token_->token_end_;
const int32_t token_UTF16_end = compound_statement_node->token_->token_UTF16_end_;
std::string &&token_string = script_string_.substr(token_start, token_end - token_start + 1);
slim_script_block_node->ReplaceTokenWithToken(new EidosToken(slim_script_block_node->token_->token_type_, token_string, token_start, token_end, token_UTF16_start, token_UTF16_end, token_line));
}
else if (!parse_make_bad_nodes_)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::Parse_SLiMEidosBlock): (internal error) missing compound_statement_node" << EidosTerminate(current_token_);
}
catch (...)
{
if (slim_script_block_node)
{
slim_script_block_node->~EidosASTNode();
gEidosASTNodePool->DisposeChunk(const_cast<EidosASTNode*>(slim_script_block_node));
}
throw;
}
return slim_script_block_node;
}
void SLiMEidosScript::ParseSLiMFileToAST(bool p_make_bad_nodes)
{
// destroy the parse root and return it to the pool; the tree must be allocated out of gEidosASTNodePool!
if (parse_root_)
{
parse_root_->~EidosASTNode();
gEidosASTNodePool->DisposeChunk(const_cast<EidosASTNode*>(parse_root_));
parse_root_ = nullptr;
}
// set up parse state
parse_index_ = 0;
current_token_ = &token_stream_.at(parse_index_); // should always have at least an EOF
current_token_type_ = current_token_->token_type_;
parse_make_bad_nodes_ = p_make_bad_nodes;
// parse a new AST from our start token
parse_root_ = Parse_SLiMFile();
parse_root_->OptimizeTree();
// if logging of the AST is requested, do that
if (gEidosLogAST)
{
std::cout << "AST : \n";
this->PrintAST(std::cout);
}
parse_make_bad_nodes_ = false;
}
bool SLiMEidosScript::StringIsIDWithPrefix(const std::string &p_identifier_string, char p_prefix_char)
{
const char *id_cstr = p_identifier_string.c_str();
size_t id_cstr_len = strlen(id_cstr);
// the criteria here are pretty loose, because we want SLiMEidosScript::ExtractIDFromStringWithPrefix to be
// called and generate a raise if the string appears to be intended to be an ID but is malformed
// however, we don't want to end up here with just any string that starts with the prefix character...
// if the prefix character is not present, then it's not a match
if ((id_cstr_len < 1) || (*id_cstr != p_prefix_char))
return false;
// if there is at least one character following the prefix, those characters must all be numeric
if (id_cstr_len > 1)
{
for (unsigned int str_index = 1; str_index < id_cstr_len; ++str_index)
if ((id_cstr[str_index] < '0') || (id_cstr[str_index] > '9'))
return false;
}
return true;
}
slim_objectid_t SLiMEidosScript::ExtractIDFromStringWithPrefix(const std::string &p_identifier_string, char p_prefix_char, const EidosToken *p_blame_token)
{
const char *id_cstr = p_identifier_string.c_str();
size_t id_cstr_len = strlen(id_cstr);
if ((id_cstr_len < 1) || (*id_cstr != p_prefix_char))
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::ExtractIDFromStringWithPrefix): an identifier prefix '" << p_prefix_char << "' was expected." << EidosTerminate(p_blame_token);
for (unsigned int str_index = 1; str_index < id_cstr_len; ++str_index)
if ((id_cstr[str_index] < '0') || (id_cstr[str_index] > '9'))
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::ExtractIDFromStringWithPrefix): the id after the '" << p_prefix_char << "' prefix must be a simple integer." << EidosTerminate(p_blame_token);
if (id_cstr_len < 2)
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::ExtractIDFromStringWithPrefix): an integer id was expected after the '" << p_prefix_char << "' prefix." << EidosTerminate(p_blame_token);
errno = 0;
char *end_scan_char = nullptr;
int64_t long_block_id = strtoll(id_cstr + 1, &end_scan_char, 10); // +1 to omit the prefix character
if (errno || (end_scan_char == id_cstr + 1))
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::ExtractIDFromStringWithPrefix): the identifier " << p_identifier_string << " was not parseable." << EidosTerminate(p_blame_token);
if ((long_block_id < 0) || (long_block_id > SLIM_MAX_ID_VALUE))
EIDOS_TERMINATION << "ERROR (SLiMEidosScript::ExtractIDFromStringWithPrefix): the identifier " << p_identifier_string << " was out of range." << EidosTerminate(p_blame_token);
return static_cast<slim_objectid_t>(long_block_id); // range check is above, with a better message than SLiMCastToObjectidTypeOrRaise()
}
//
// SLiMEidosBlock
//
#pragma mark -
#pragma mark SLiMEidosBlock
#pragma mark -
SLiMEidosBlockType SLiMEidosBlock::BlockTypeForRootNode(EidosASTNode *p_root_node)
{
// Get the block type for a node without actually constructing the block. This is parallel to the constructor code below,
// and the two must be maintained in parallel when new callback types, etc., are added. Note that we don't do any bounds-
// or error-checking here; we just need to know the *intended* block type, if we can figure it out.
const std::vector<EidosASTNode *> &block_children = p_root_node->children_;
int child_index = 0, n_children = (int)block_children.size();
if ((n_children == 1) && (block_children[child_index]->token_->token_type_ == EidosTokenType::kTokenFunction))
{
return SLiMEidosBlockType::SLiMEidosUserDefinedFunction;
}
else
{
// eat a string, for the script id, if present; an identifier token must follow the sX format to be taken as an id here, as in the parse code
if (child_index < n_children)
{
EidosToken *script_id_token = block_children[child_index]->token_;
if ((script_id_token->token_type_ == EidosTokenType::kTokenIdentifier) && SLiMEidosScript::StringIsIDWithPrefix(script_id_token->token_string_, 's'))
child_index++;
}
// eat the optional tick range, which could be X, X:Y, X:, or :Y
if ((child_index < n_children) && (block_children[child_index]->token_->token_type_ == EidosTokenType::kTokenNumber))
child_index++;
if ((child_index < n_children) && (block_children[child_index]->token_->token_type_ == EidosTokenType::kTokenColon))
child_index++;
if ((child_index < n_children) && (block_children[child_index]->token_->token_type_ == EidosTokenType::kTokenNumber))
child_index++;
// eat the callback info node, if present
if (child_index < n_children)
{
const EidosASTNode *callback_node = block_children[child_index];
const EidosToken *callback_token = callback_node->token_;
if (callback_token->token_type_ == EidosTokenType::kTokenIdentifier)
{
const std::string &callback_name = callback_token->token_string_;
if (callback_name.compare(gStr_first) == 0)
return SLiMEidosBlockType::SLiMEidosEventFirst;
else if (callback_name.compare(gStr_early) == 0)
return SLiMEidosBlockType::SLiMEidosEventEarly;
else if (callback_name.compare(gStr_late) == 0)
return SLiMEidosBlockType::SLiMEidosEventLate;
else if (callback_name.compare(gStr_initialize) == 0)
return SLiMEidosBlockType::SLiMEidosInitializeCallback;
else if (callback_name.compare(gStr_fitnessEffect) == 0)
return SLiMEidosBlockType::SLiMEidosFitnessEffectCallback;
else if (callback_name.compare(gStr_mutationEffect) == 0)
return SLiMEidosBlockType::SLiMEidosMutationEffectCallback;
else if (callback_name.compare(gStr_mutation) == 0)
return SLiMEidosBlockType::SLiMEidosMutationCallback;
else if (callback_name.compare(gStr_interaction) == 0)
return SLiMEidosBlockType::SLiMEidosInteractionCallback;
else if (callback_name.compare(gStr_mateChoice) == 0)
return SLiMEidosBlockType::SLiMEidosMateChoiceCallback;
else if (callback_name.compare(gStr_modifyChild) == 0)
return SLiMEidosBlockType::SLiMEidosModifyChildCallback;
else if (callback_name.compare(gStr_recombination) == 0)
return SLiMEidosBlockType::SLiMEidosRecombinationCallback;
else if (callback_name.compare(gStr_survival) == 0)
return SLiMEidosBlockType::SLiMEidosSurvivalCallback;
else if (callback_name.compare(gStr_reproduction) == 0)
return SLiMEidosBlockType::SLiMEidosReproductionCallback;
}
}
}
return SLiMEidosBlockType::SLiMEidosNoBlockType;
}
SLiMEidosBlock::SLiMEidosBlock(EidosASTNode *p_root_node) :
self_symbol_(gID_self, EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_SLiMEidosBlock_Class))),
script_block_symbol_(gEidosID_none, EidosValue_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Object(this, gSLiM_SLiMEidosBlock_Class))),
root_node_(p_root_node)
{
// self_symbol_ is always a constant, but can't be marked as such on construction
self_symbol_.second->MarkAsConstant();
script_block_symbol_.second->MarkAsConstant();
// NOTE: SLiMEidosBlock::BlockTypeForRootNode() above must be maintained in parallel with this method!
const std::vector<EidosASTNode *> &block_children = root_node_->children_;
int child_index = 0, n_children = (int)block_children.size();
block_id_ = -1; // the default unless it is set below
if ((n_children == 1) && (block_children[child_index]->token_->token_type_ == EidosTokenType::kTokenFunction))
{
EidosASTNode *function_decl_node = block_children[child_index];
if (function_decl_node->children_.size() == 4)
{
compound_statement_node_ = function_decl_node->children_[3];
type_ = SLiMEidosBlockType::SLiMEidosUserDefinedFunction;
child_index++;
}
else
{
EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SLiMEidosBlock): (internal error) unexpected child count in user-defined function declaration." << EidosTerminate(function_decl_node->token_);
}
}
else
{
// eat a string, for the script id, if present; an identifier token must follow the sX format to be taken as an id here, as in the parse code
if (child_index < n_children)
{
EidosToken *script_id_token = block_children[child_index]->token_;
if ((script_id_token->token_type_ == EidosTokenType::kTokenIdentifier) && SLiMEidosScript::StringIsIDWithPrefix(script_id_token->token_string_, 's'))
{
block_id_ = SLiMEidosScript::ExtractIDFromStringWithPrefix(script_id_token->token_string_, 's', script_id_token);
child_index++;
// fix ID string for our symbol
std::string new_symbol_string = SLiMEidosScript::IDStringWithPrefix('s', block_id_);
script_block_symbol_.first = EidosStringRegistry::GlobalStringIDForString(new_symbol_string);
}
}
// eat the optional tick range, which could be X, X:Y, X:, or :Y
// we don't need to syntax-check here since the parse already did
// BCH 3/6/2024: Note that X and Y can now be expressions, not just numbers; we evaluate them later, in EvaluateScriptBlockTickRanges()
if (child_index < n_children)
{
EidosToken *start_tick_token = block_children[child_index]->token_;
// BCH 3/6/2024: We used to parse a number here, and get its value immediately:
//
//if (start_tick_token->token_type_ == EidosTokenType::kTokenNumber)
//{
// int64_t long_start = EidosInterpreter::NonnegativeIntegerForString(start_tick_token->token_string_, start_tick_token);
//
// // We do our own range checking here so that we can highlight the bad token
// if ((long_start < 1) || (long_start > SLIM_MAX_TICK))
// EIDOS_TERMINATION << "ERROR (SLiMEidosBlock::SLiMEidosBlock): the start tick " << start_tick_token->token_string_ << " is out of range." << EidosTerminate(start_tick_token);
//
// start_tick_ = SLiMCastToTickTypeOrRaise(long_start);
// end_tick_ = start_tick_; // if a start is given, the default end is the same as the start
// child_index++;
//}
//
// but now we allow any expression, and evaluate it later (as long as it is not a colon):
if ((start_tick_token->token_type_ != EidosTokenType::kTokenColon) && !SLiM_TokenIsCallbackIdentifier(start_tick_token))
{
start_tick_node_ = block_children[child_index];
child_index++;
}
}
if (child_index < n_children)
{
EidosToken *colon_token = block_children[child_index]->token_;