This repository has been archived by the owner on May 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pb.go
1035 lines (884 loc) · 26.9 KB
/
pb.go
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
package antlraci
/*
pb.go contains all Permission, Bind Rules and Permission+Bind Rules
types and methods.
*/
import (
//"github.com/antlr4-go/antlr/v4"
"github.com/JesseCoretta/go-stackage"
)
/*
Permission defines the rights and disposition of a single Permission
+ Bind Rule pair.
*/
type Permission struct {
Allow *bool // ptr for safety reasons
Rights []string
}
/*
String is a stringer method that returns the string
representation of the receiver instance.
*/
func (r Permission) String() string {
if len(r.Rights) == 0 {
return ``
}
switch *r.Allow {
case true:
return sprintf("allow(%s)", join(r.Rights, `,`))
case false:
return sprintf("deny(%s)", join(r.Rights, `,`))
}
return `<invalid_permission_disposition>`
}
/*
PermissionBindRule contains a single Permission instance, and
a single BindRules instance.
*/
type PermissionBindRule struct {
P Permission
B stackage.Stack
}
/*
String is a stringer method that returns the string
representation of the receiver instance.
*/
func (r PermissionBindRule) String() string {
if len(r.P.Rights) == 0 {
return ``
}
if r.B.Len() == 0 {
return ``
}
return sprintf("%s %s;",
r.P.String(),
r.B.String())
}
/*
ParsePermission processes a permission statement and returns the disposition,
the rights identifiers and a boolean value indicative of a successful parse.
The disposition boolean instance, when true, indicates 'allow'; false indicates
'deny'.
The slices of rights identifiers must be non-zero in length for a successful
return result.
*/
func ParsePermission(raw string) (perm Permission, err error) {
var p *ACIParser
if p, err = initAntlr(raw); err != nil {
return
}
// Perform an extra check to ensure that what
// went in also came out as expected.
if perm, err = processBindPermission(p.Permission()); err == nil {
got := perm.String()
want := raw
if rplc(got, ` `, ``) != rplc(want, ` `, ``) {
err = errorf("%T parse failed; unexpected result (bad syntax?): [%s]",
perm, got)
return perm, err
}
}
return
}
/*
processBindPermission is the handler for a PermissionBindRule's 'permission' segment.
*/
func processBindPermission(p IPermissionContext) (r Permission, err error) {
// if nothing exists, this is a bogus context.
if p == nil {
err = errorf("%T instance is nil, cannot proceed", p)
return
}
// grab all rights.
if r.Rights, err = processPermissionRights(p.AllPrivilege()); err != nil {
return
}
// grab the disposition.
r.Allow, err = processPermissionDisposition(p.Disposition())
return
}
/*
processPermissionDisposition returns a pointer to a bool alongside an error. This is a
risky area, and we want to avoid false positives especially in the case of a permission
disposition. To do this, we use a double mutex boolean check to ensure ALLOW is "on"
AND DENY is "off" --OR-- ALLOW is "off" AND DENY is "on". Furthermore, we need to avoid
the otherwise helpful bool defaults that would be imposed, therefore this is the reason
we use the bool pointer, versus a literal.
*/
func processPermissionDisposition(d IDispositionContext) (disp *bool, err error) {
if d == nil {
err = errorf("%T instance is nil, cannot proceed", d)
return
}
// Determine the permission disposition, or fail
// if not an anticipated result ...
granted := d.GrantedPermission()
withheld := d.WithheldPermission()
if granted != nil && withheld == nil {
// Confirmed 'allow'
disp = new(bool)
*disp = granted.ALLOW() != nil
} else if granted == nil && withheld != nil {
// Confirmed 'deny'
disp = new(bool)
*disp = false // !! HARD-CODED FALSE FOR SECURITY REASONS -- DO NOT CHANGE !!
} else {
err = errorf("%T parse failed: permission disposition unspecified or ambiguous (hint: choose ONE of 'allow' OR 'deny'", d)
}
return
}
/*
processPermissionRights returns the string representation of each privilege (right)
found within the sequence of IPrivilegeContext instances (privs). If the resolution
of any right fails, or if the resulting number does *not* equal the length of the
IPrivilegeContext slices provided as input, an error is returned alongside an empty
list of privileges
*/
func processPermissionRights(privs []IPrivilegeContext) (rights []string, err error) {
// Empty context slices
if len(privs) == 0 {
err = errorf("Empty %T", privs)
return
}
// empty context slice (just check #0)
if privs[0].GetChildCount() == 0 {
err = errorf("No rights found in %T", privs)
return
}
// obtain slices of rights identifiers, appending
// each to the return slice type.
for i := 0; i < len(privs); i++ {
priv := privs[i]
if priv.GetChildCount() == 0 || priv.IsEmpty() {
err = errorf("Empty or childless %T specifier", priv)
return
}
rights = append(rights, priv.GetText())
}
var (
pl int = len(privs) // privs len
rl int = len(rights) // result len
)
if pl != rl {
err = errorf("Failed to parse all righti specifiers in %T; expected '%d', found '%d'",
privs, pl, rl)
}
return
}
/*
ParseBindRule parses a single bind rule expression, e.g.: 'userdn = "ldap:///anyone"',
and returns a stackage.Condition instance alongside an error.
*/
func ParseBindRule(raw string) (r stackage.Condition, err error) {
// Strip L/R parentheticals out to avoid needlessly
// fatal errors, but remember their state for the
// assignment to an otherwise valid return instance.
var isparen bool
stripped := trimParen(raw)
if raw != stripped {
isparen = true
}
var p *ACIParser
if p, err = initAntlr(stripped); err != nil {
return
}
// Parse and return our BindRule instance.
// Also make sure to mark the instance as
// parenthetical when applicable ...
r, err = processBindRule(p.BindRule())
r.Paren(isparen)
return
}
/*
processBindRule processes the tokens found within a bind rule. A
bind rule (in its singular manifestation) is a bind keyword, a
comparison operator and a sequence of one (1) or more expression
values (with symbolic OR (||) delimitation when needed). Therefore
we need only match the contexts of those three (3) components.
A stackage.Condition, bearing the bind rule components, is returned
alongside an error.
*/
func processBindRule(ibrc IBindRuleContext) (r stackage.Condition, err error) {
if ibrc == nil {
err = errorf("%T instance is nil; cannot proceed")
return
}
var ct int = ibrc.GetChildCount()
r.Init()
// iterate child components of the bind rule:
// keyword, operator, value(s).
for k := 0; k < ct; k++ {
switch tv := ibrc.GetChild(k).(type) {
case *BindKeywordContext:
var kw string
if kw, err = processRuleKeyword(tv, `bind`); err != nil {
return
}
r.SetKeyword(kw)
case *BindOperatorContext:
var op stackage.ComparisonOperator
if op, err = processRuleOperator(tv, r.Keyword()); err != nil {
return
}
r.SetOperator(op)
case *ExpressionValuesContext:
var ex RuleExpression
if ex, err = processRuleExpression(tv); err != nil {
return
}
r.SetExpression(ex)
}
}
// Perform a basic go-stackage validity check of
// the newly added contents ...
if err = r.Valid(); err == nil {
// No error: stamp the return value as
// known to be a valid BindRule
r.SetCategory(`bind`)
} else {
// Some validity check failed: mark the
// return as a bogus BindRule
r.SetCategory(`<invalid_bind>`)
}
return
}
/*
ParseBindRules returns a stackage.Stack containing a Bind Rules
expression (which may or may not involving nesting). An instance
of error is returned alongisde the resulting stack.
*/
func ParseBindRules(raw string) (b stackage.Stack, err error) {
var p *ACIParser
if p, err = initAntlr(raw); err != nil {
return
}
// Parse and return our BindRules instance, which
// (hopefully) contains one (1) or more Bind Rule(s).
brs := p.BindRules()
if _, balanced := parentheticalContextCount(brs); !balanced {
err = errorf("Unbalanced parenthetical expression in %T", brs)
return
}
w, _ := booleanContextFromBindRules(brs)
if b, err = processBindRules(brs, 0, isParentheticalContext(brs), w); err == nil {
got := rplc(b.String(), ` `, ``)
want := rplc(raw, ` `, ``)
if got != want {
err = errorf("BindRules parse failed; unexpected result (bad syntax?): expected '%s', got '%s'",
want, got)
}
}
return
}
func initIOStack(ctx IBindRulesContext, word ...string) (cc, in stackage.Stack) {
cc = stackageBasic() // contiguous condition stack, temporary use
in = newStack() // uninitialized, we dont know the bool word yet.
if len(word) > 0 {
in, _ = stackByBoolOp(word[0])
} else {
if nxw, found := booleanContextFromBindRules(ctx); found {
in, _ = stackByBoolOp(nxw)
in.Paren(isParentheticalContext(ctx))
} else {
in = stackageBasic()
}
}
return
}
/*
processBindRules converts the Bind Rules token stream into a stackage.Stack
instance, which is returned alongside an error. Nested stacks and any opener
or closer encapsulating parentheticals will be honored wherever possible.
*/
func processBindRules(ctx IBindRulesContext, depth int, oparen bool, boolword ...string) (outer stackage.Stack, err error) {
if ctx == nil {
err = errorf("%T instance is nil; cannot proceed", ctx)
return
}
var iparen bool = isParentheticalContext(ctx)
var nxw string
var cct int = ctx.GetChildCount()
var cc, inner stackage.Stack
cc, inner = initIOStack(ctx, boolword...)
// iterate on all child objects, performing
// recursive calls wherever needed.
for c := 0; c < cct; c++ {
// use the child indices to call the
// slice from the antlr children array.
child := ctx.GetChild(c)
// is the current token (child, #c) within this
// context sequence (ctx) parenthetical itself?
//var bparen bool = currentTokenIsParenthetical(c, ctx)
// perform type switch on current child instance
// iteration ...
switch {
// context type is a nested bind rules (NOTE:
// mind the plurality!)
case isBindRulesContext(child):
if cc.Len() > 0 {
cc.Transfer(inner)
cc.Reset()
}
if inner, err = handleBindRulesContext(
child.(*BindRulesContext),
inner,
nxw,
iparen || currentTokenIsParenthetical(c, ctx),
depth); err != nil {
return
}
if !hasSfx(nxw, `NOT`) && !hasSfx(trimS(inner.Kind()), `NOT`) {
x, _ := stackByBoolOp(nxw)
inner.Transfer(x)
inner = x
} else {
// If inner is of a length greater than
// one (1), and since it is a NOTed stack,
// begin special handling for qualified
// candidate stacks needing disenvelopment.
if inner.Len() > 1 {
inner = disenvelopNestedNot(nxw, inner)
}
}
// wipe out nxw, since it survives loops and influences
// what kind of stacks are (or will be) created.
nxw = ``
// context type is a nested bind rule,
// (NOTE: singular component of a bind
// rules context)
case isBindRuleContext(child):
var br stackage.Condition
if br, err = processBindRule(child.(*BindRuleContext)); err != nil {
return
}
if !cc.IsParen() {
cc.Push(br)
}
// context type is a parenthetical
// child, either an opener (left)
// or a closer (right).
case isParentheticalContext(child):
_, depth = handleParentheticalContext(c, depth, oparen, child)
// context type is a Boolean logical
// WORD operator, one of the following:
// 'AND', 'OR', 'AND NOT'.
case isBooleanWordContext(child):
_, nxw = handleBooleanWordContext(child, inner)
}
}
// We've exited the child context loop(s)
// and have arrived at the final assembly
// stage. Before we begin iterating the
// inner stack, let's xfer the stack of
// contiguous conditions, if applicable.
if cc.Len() > 0 {
cc.Transfer(inner)
}
// scan for any pointless enveloping
// that might have occurred, and then
// add inner to the return stack outer.
//outer, err = cleanupBindStack(outer, inner, oparen)
if inner.Len() == 0 {
err = errorf("inner stack (%T) contains no slices", inner)
return
}
outer = inner
// Defrag+Reveal
outer.Defrag().
Reveal()
return
}
func booleanContextFromBindRules(ctx IBindRulesContext) (w string, found bool) {
ct := ctx.GetChildCount()
if ct != 3 {
return
}
// get the "middle slice", which ought
// to be a Boolean WORD.
switch tv := ctx.GetChild(1).(type) {
case *WordAndContext:
if tv != nil {
w = `AND`
}
case *WordOrContext:
if tv != nil {
w = `OR`
}
case *WordNotContext:
if tv != nil {
w = `AND NOT`
}
}
found = len(w) > 0
return
}
func isBooleanWordContext(ctx any) (is bool) {
switch tv := ctx.(type) {
case *WordAndContext:
is = tv != nil
case *WordOrContext:
is = tv != nil
case *WordNotContext:
is = tv != nil
}
return
}
/*
get rid of this
*/
func handleBooleanWordContext(ctx any, child stackage.Stack) (stackage.Stack, string) {
// nxw contains the (probable) Boolean
// logical WORD operator for the next
// stack.
var nxw string
switch tv := ctx.(type) {
case *WordOrContext:
nxw = tv.GetText()
case *WordAndContext:
nxw = tv.GetText()
case *WordNotContext:
nxw = tv.GetText()
}
return child, nxw
}
func isBindRuleContext(ctx any) bool {
if assert, is := ctx.(*BindRuleContext); is {
return assert != nil
}
return false
}
func isBindRulesContext(ctx any) bool {
if assert, is := ctx.(*BindRulesContext); is {
return assert != nil
}
return false
}
/*
handleBindRulesContext handles the recursive operations associated with a nested stack (BindRulesContext)
encountered during processBindRules execution. A populated incarnation of dest is returned, hopefully
containing one (1) or more elements obtained from the ctx input instance. An error is returned alongside
the stack instance should processing fail for any reason.
*/
func handleBindRulesContext(ctx *BindRulesContext, dest stackage.Stack, nxw string, paren bool, depth int) (stackage.Stack, error) {
var (
err error
key string // type of stack (e.g.: and, list, etc)
rec stackage.Stack // stack returned via recursive call
)
if ctx.GetChildCount() == 0 {
err = errorf("%T contains no children to process", ctx)
return dest, err
}
// Spawn a new recursion of the CALLER of this function.
if rec, err = processBindRules(ctx, depth, paren, nxw); err != nil {
return dest, err
}
if rec.Len() == 0 {
err = errorf("%T recursion returned empty sub-stack", ctx)
return dest, err
}
key = trimS(rec.Kind())
if eq(key, `not`) {
if !eq(trimS(rec.Kind()), `NOT`) {
rec, _ = disenvelopStack(rec)
dest.Push(rec)
} else {
dest.Push(rec)
}
rec.Paren(paren && contextIsParenthetical(ctx)) // fixes #30
} else {
rec.Paren(paren)
dest.Push(rec)
}
if dest.Len() == 0 {
err = errorf("dest stack (%T) contains no slices", dest)
}
return dest, err
}
func disenvelopNestedNot(word string, inner stackage.Stack) stackage.Stack {
// Obtain the last slice present within
// the inner stack. If not found, then
// return the inner stack untouched.
slice, ok := inner.Index(inner.Len() - 1)
if !ok {
return inner
}
// Type assert the obtained (any) slice from
// the inner stack. If not a stackage.Stack,
// return the inner stack untouched.
assert, asserted := slice.(stackage.Stack)
if !asserted {
return inner
}
// Ensure the asserted stack has a label
// of 'NOT'. If not NOT (lol), we return
// the inner stack untouched.
if !hasSfx(trimS(assert.Kind()), `NOT`) {
return inner
}
// This function only applies to singular
// nested NOT contexts. If no nesting has
// been detected, return the inner stack
// untouched.
if !assert.IsNesting() {
return inner
}
// Disenvelop the asserted stack, revealing
// the contents exactly one (1) level down.
// If disenvelopment fails, return the inner
// stack untouched. Else, dise var now ready
// for interrogation.
dise, eok := disenvelopStack(assert)
if !eok {
return inner
}
// This function applies to singular
// nested NOT contexts only! Thus, a
// length of one (1) is required.
if dise.Len() != 1 {
return inner
}
// Create a new stack identified by 'word'
// or, if unidentifiable, return the inner
// stack untouched.
naught, idd := stackByBoolOp(word)
if !idd {
return inner
}
////////////////////////////////////////
// Begin point of no return (CHANGES
// TO INNER ARE ABOUT TO HAPPEN).
////////////////////////////////////////
dise.Transfer(naught) // transcribe contents from dise (old) -> naught (new)
naught.Paren(dise.IsParen()) // preserve parentheticals
dise = naught // clobber dise var with new ptr
envl, _ := stackByBoolOp(trimS(inner.Kind())) // create new stack based on INNER's kind
idx := inner.Len() - 1 // preserve slice number (idx), as we'll be wiping out the stack
inner.Remove(idx) // Wipe out idx slice containing that which we are re-enveloping
inner.Transfer(envl) // Move everything from inner into new envelope stack (envl)
inner.Reset() // Remove all contents of inner (don't clobber yet)
envl.Insert(dise, idx) // Insert original disenveloped NOT into new envelope at slice #idx
inner = envl // clobber inner for return
return inner
}
/*
ParsePermissionBindRule performs the same operations as ParsePermission
and ParseBindRules, except it processes them together as they normally would
appear in a complete Instruction.
*/
func ParsePermissionBindRule(raw string) (r PermissionBindRule, err error) {
var p *ACIParser
if p, err = initAntlr(raw); err != nil {
return
}
// grab whatever PBR is there, and make sure
// return is not nil. We'll only be targeting
// the first index going forward.
pbr := p.PermissionBindRule()
if pbr == nil {
err = errorf("%T instance is nil; cannot proceed", pbr)
return
}
// Verify what we got back, assuming no errors
// were reported
if r, err = processPermissionBindRule(pbr); err == nil {
got := rplc(r.String(), ` `, ``)
want := rplc(raw, ` `, ``)
if got != want {
err = errorf("%T parse failed; unexpected result (bad syntax?): expected '%s', got '%s'",
r, want, got)
}
}
return
}
/*
processPermissionBindRule is a private parser function called by ParsePermissionBindRule
and ParsePermissionBindRules. It returns an instance of PermissionBindRule alongside an
error instance.
*/
func processPermissionBindRule(pbr IPermissionBindRuleContext) (r PermissionBindRule, err error) {
// make sure the input was terminated properly
if pbr.RuleTerminator() == nil {
err = errorf("Permission+Bind rule is not terminated properly (hint: ';')")
return
} else if hasPfx(pbr.RuleTerminator().GetText(), `<missing`) {
err = errorf("Permission+Bind rule is not terminated properly (hint: ';')")
return
}
// Obtain the permission portion of the PB
if r.P, err = processBindPermission(pbr.Permission()); err != nil {
return
}
// obtain and verify bind rules stackage.Stack ...
brs := pbr.BindRules()
w, _ := booleanContextFromBindRules(brs)
r.B, err = processBindRules(brs, 0, isParentheticalContext(brs), w)
return
}
/*
ParsePermissionBindRules processes a single text value that expresses multiple
PermissionBindRule instances in sequence. A stackage.Stack instance, containing
zero (0) or more PermissionBindRule instances is returned alongside an error.
*/
func ParsePermissionBindRules(raw string) (p stackage.Stack, err error) {
var _p *ACIParser
if _p, err = initAntlr(raw); err != nil {
return
}
pbrs := _p.PermissionBindRules()
p, err = processPermissionBindRules(pbrs)
return
}
/*
processPermissionBindRules is a private parser function called by ParsePermissionBindRules.
*/
func processPermissionBindRules(ctx IPermissionBindRulesContext) (r stackage.Stack, err error) {
var ct int = ctx.GetChildCount()
if ct == 0 {
err = errorf("Empty %T, nothing to parse", ctx)
return
}
r = stackage.List()
// iterate the slices of IPermissionBindRuleContext
// provided by ANTLR to attempt to parse them into
// individual instances of PermissionBindRule. If
// successful, push into return stack (r).
for _, _pbr := range ctx.AllPermissionBindRule() {
var pbr PermissionBindRule
if pbr, err = processPermissionBindRule(_pbr); err != nil {
return
}
r.Push(pbr)
}
var rl int = r.Len()
if rl != ct {
err = errorf("Failed to parse one or more %T slices; expected '%d', received '%d'", ctx, ct, rl)
}
return
}
/*
isNextTokenBoolOp returns a boolean WORD operator string value (w)
alongside a presence-indicative boolean value (is). This function
scans the BindRulesContext ahead by exactly one (1) token. If the
next token is AND, OR or (AND) NOT, the string representation is
returned.
If a CLOSING parenthesis (right) is encountered, this function will
recurse into another call of itself, scanning ahead by one (1) more
index in an attempt to find the "next" logical WORD operator.
*/
func isNextTokenBoolOp(i int, ctx IBindRulesContext) (w string, is bool) {
if i+1 >= ctx.GetChildCount() {
return
}
switch ctx.GetChild(i + 1).(type) {
case *WordAndContext:
w = `AND`
is = true
case *WordOrContext:
w = `OR`
is = true
case *WordNotContext:
w = `NOT`
is = true
}
return
}
func handleParentheticalContext(idx, depth int, oparen bool, ctx any) (paren bool, d int) {
// perform context type switch
switch tv := ctx.(type) {
// context type(s) are left/opener, meaning
// this stack is parenthetical.
case *OpeningParenthesisContext:
if hasPfx(tv.GetText(), `<missing`) {
break
}
if idx == 0 {
if idx == 0 {
paren = oparen && depth == 0
}
depth++
}
// context type(s) are right/closer.
case *ClosingParenthesisContext:
if hasPfx(tv.GetText(), `<missing`) {
break
}
depth--
}
d = depth
return
}
/*
currentTokenIsParenthetical is a convenient wrapper for getLN,
but only reports a boolean value indicative of whether this
index (i) resides between an opening parenthesis (left) and a
closing parenthesis (right). Both of these conditions must be
true for a true return value.
This function is distinct from contextIsParenthetical, as that
function looks INSIDE a context to discern its parenthetical
state. This function, on the other hand, analyzes a token within
a stream that is currently being iterated, using index (i) for
placement.
*/
func currentTokenIsParenthetical(i int, ctx IBindRulesContext) bool {
var res [2]bool
// get the previous and upcoming tokens from the
// context using the i as the current positional
// index. Both l and r must be non nil.
if l, n := getLN(i, ctx); l != nil && n != nil {
for i, val := range []any{
l, // last (previous)
n, // next (upcoming)
} {
switch tv := val.(type) {
case *OpeningParenthesisContext:
if !hasPfx(tv.GetText(), `<missing`) {
res[i] = i == 0
}
case *ClosingParenthesisContext:
if !hasPfx(tv.GetText(), `<missing`) {
res[i] = i == 1
}
}
}
}
// regardless of the result above, evaluate
// the boolean pair using &&.
return res[0] && res[1]
}
/*
contextIsParenthetical reviews the first (0) and final (len-1)
context child values in order to determine whether the former
is an *OpeningParenthesisContext and the latter is its partner
*ClosingParenthesisContext. Note that a return of true doesn't
necessarily indicate the expression itself is parenthetical,
meaning there could be two (2) parenthetical sets, bound by a
Boolean WORD operator, which might still return true because
the first and last are openers and closers respectively, even
if they're not "mates". This is merely one (1) check of several
that might allow for logical deduction regarding the effective
parenthetical state of a context.
*/
func contextIsParenthetical(ctx IBindRulesContext) (ok bool) {
count := ctx.GetChildCount()
// In order for this evaluation to have
// any meaning, the child count MUST be
// three (3). Any parenthetical context
// shall appear (logically) as:
//
// 0 1 2
// OPEN_PAREN SOMETHING CLOSE_PAREN
//
// The 'something' (slice #1) should be
// a Bind Rule -OR- Bind RuleS context.
if count != 3 {
return
}
f := ctx.GetChild(0) // first (left) child
l := ctx.GetChild(count - 1) // final (right) child
// Analyze first (f) and last (l) tokens acquired
// through GetChild index calls above ...
var x any
if x, ok = f.(*OpeningParenthesisContext); ok && x != nil {
if hasPfx(f.(*OpeningParenthesisContext).GetText(), `<missing`) {
return
}
// The first token was confirmed to be a
// non-nil opening parenthetical context.
if x, ok = l.(*ClosingParenthesisContext); ok && x != nil {
if hasPfx(l.(*OpeningParenthesisContext).GetText(), `<missing`) {
return
}
// The final token was confirmed to be a
// non-nil closing parenthetical context.
// Before we finish, look at the "middle"
// token. If it is either a Bind Rule or
// Bind RuleS context, return true IF non
// nil.
if x, ok = ctx.GetChild(1).(*BindRulesContext); ok {
ok = x != nil
} else if x, ok = ctx.GetChild(1).(*BindRuleContext); ok {
ok = x != nil
}
}
}
return
}
/*
isParentheticalContext returns a boolean value alongside a depth-indicative
integer. The boolean value, when true, indicates the context (ctx) input by
the caller is parenthetical in nature (e.g.: either a left/opening parenthesis
character (ASCII #40) or a right/closing parenthesis (ASCII #41) character).
This is generalized for use in switch/case matching.
*/
func isParentheticalContext(ctx any) (is bool) {
// perform context type switch
switch tv := ctx.(type) {
case *OpeningParenthesisContext:
if !hasPfx(tv.GetText(), `<missing`) {
is = true
}
case *ClosingParenthesisContext:
if !hasPfx(tv.GetText(), `<missing`) {
is = true
}
}
return
}
/*
parentheticalContextCount will scan the IBindRulesContext (ctx) instance for
parenthetical opener and closer characters (ASCII #40 and #41 respectively).
A complete pair, that is: one (1) opener and one (1) closer, shall result in
a +1 to the return value. Placement of these parenthetical characters is not
significant. This function is only interested in the number of occurrences,
and not in their distribution.
This function is useful for analyzing contexts which have nested parenthetical
stacks in an unusual -- but legal -- manner.
*/
func parentheticalContextCount(ctx IBindRulesContext) (int, bool) {
count := ctx.GetChildCount()
if count <= 1 {
return 0, true
}
var pair int
var fs int
var l, r int
for i := 0; i < count; i++ {
switch tv := ctx.GetChild(i).(type) {
case *OpeningParenthesisContext:
if !hasPfx(tv.GetText(), `<missing`) {
fs++
l++
}
case *ClosingParenthesisContext:
if fs == 1 && !hasPfx(tv.GetText(), `<missing`) {