-
-
Notifications
You must be signed in to change notification settings - Fork 311
/
parser.y
1515 lines (1485 loc) · 34.4 KB
/
parser.y
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
// Mgmt
// Copyright (C) 2013-2024+ James Shubin and the project contributors
// Written by James Shubin <[email protected]> and the project contributors
//
// This program 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.
//
// This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
%{
package parser
import (
"fmt"
"strings"
"github.com/purpleidea/mgmt/lang/ast"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util"
)
const (
errstrParseAdditionalEquals = "additional equals in bind statement"
errstrParseExpectingComma = "expecting trailing comma"
)
func init() {
yyErrorVerbose = true // set the global that enables showing full errors
}
%}
%union {
row int
col int
//err error // TODO: if we ever match ERROR in the parser
bool bool
str string
int int64 // this is the .int as seen in lexer.nex
float float64
typ *types.Type
stmts []interfaces.Stmt
stmt interfaces.Stmt
exprs []interfaces.Expr
expr interfaces.Expr
mapKVs []*ast.ExprMapKV
mapKV *ast.ExprMapKV
structFields []*ast.ExprStructField
structField *ast.ExprStructField
args []*interfaces.Arg
arg *interfaces.Arg
resContents []ast.StmtResContents // interface
resField *ast.StmtResField
resEdge *ast.StmtResEdge
resMeta *ast.StmtResMeta
edgeHalfList []*ast.StmtEdgeHalf
edgeHalf *ast.StmtEdgeHalf
}
%token OPEN_CURLY CLOSE_CURLY
%token OPEN_PAREN CLOSE_PAREN
%token OPEN_BRACK CLOSE_BRACK
%token IF ELSE
%token BOOL STRING INTEGER FLOAT
%token EQUALS DOLLAR
%token COMMA COLON SEMICOLON
%token ELVIS DEFAULT ROCKET ARROW DOT
%token BOOL_IDENTIFIER STR_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER
%token MAP_IDENTIFIER STRUCT_IDENTIFIER VARIANT_IDENTIFIER
%token IDENTIFIER CAPITALIZED_IDENTIFIER
%token FUNC_IDENTIFIER
%token CLASS_IDENTIFIER INCLUDE_IDENTIFIER
%token IMPORT_IDENTIFIER AS_IDENTIFIER
%token COMMENT ERROR
%token PANIC_IDENTIFIER
// precedence table
// "Operator precedence is determined by the line ordering of the declarations;
// the higher the line number of the declaration (lower on the page or screen),
// the higher the precedence."
// From: https://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html
// FIXME: a yacc specialist should check the precedence and add more tests!
%left AND OR
%nonassoc LT GT LTE GTE EQ NEQ // TODO: is %nonassoc correct for all of these?
%left PLUS MINUS
%left MULTIPLY DIVIDE
%right NOT
//%right EXP // exponentiation
%nonassoc ARROW // XXX: is %nonassoc correct for this?
%nonassoc DEFAULT // XXX: is %nonassoc correct for this?
%nonassoc OPEN_BRACK // XXX: is %nonassoc correct for this?
%nonassoc IN // XXX: is %nonassoc correct for this?
%error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET BOOL CLOSE_CURLY: errstrParseExpectingComma
%error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET STRING CLOSE_CURLY: errstrParseExpectingComma
%error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET INTEGER CLOSE_CURLY: errstrParseExpectingComma
%error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET FLOAT CLOSE_CURLY: errstrParseExpectingComma
%error var_identifier EQ BOOL: errstrParseAdditionalEquals
%error var_identifier EQ STRING: errstrParseAdditionalEquals
%error var_identifier EQ INTEGER: errstrParseAdditionalEquals
%error var_identifier EQ FLOAT: errstrParseAdditionalEquals
%%
top:
prog
{
posLast(yylex, yyDollar) // our pos
// store the AST in the struct that we previously passed in
lp := cast(yylex)
lp.ast = $1.stmt
// this is equivalent to:
//lp := yylex.(*Lexer).parseResult
//lp.(*lexParseAST).ast = $1.stmt
}
;
prog:
/* end of list */
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtProg{
Body: []interfaces.Stmt{},
}
}
| prog stmt
{
posLast(yylex, yyDollar) // our pos
// TODO: should we just skip comments for now?
//if _, ok := $2.stmt.(*ast.StmtComment); !ok {
//}
if stmt, ok := $1.stmt.(*ast.StmtProg); ok {
stmts := stmt.Body
stmts = append(stmts, $2.stmt)
$$.stmt = &ast.StmtProg{
Body: stmts,
}
}
}
;
stmt:
COMMENT
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtComment{
Value: $1.str,
}
}
| bind
{
posLast(yylex, yyDollar) // our pos
$$.stmt = $1.stmt
}
| panic
{
posLast(yylex, yyDollar) // our pos
$$.stmt = $1.stmt
}
| resource
{
posLast(yylex, yyDollar) // our pos
$$.stmt = $1.stmt
}
| edge
{
posLast(yylex, yyDollar) // our pos
$$.stmt = $1.stmt
}
| IF expr OPEN_CURLY prog CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtIf{
Condition: $2.expr,
ThenBranch: $4.stmt,
//ElseBranch: nil,
}
}
| IF expr OPEN_CURLY prog CLOSE_CURLY ELSE OPEN_CURLY prog CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtIf{
Condition: $2.expr,
ThenBranch: $4.stmt,
ElseBranch: $8.stmt,
}
}
// this is the named version, iow, a user-defined function (statement)
// `func name() { <expr> }`
// `func name(<arg>) { <expr> }`
// `func name(<arg>, <arg>) { <expr> }`
| FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtFunc{
Name: $2.str,
Func: &ast.ExprFunc{
Args: $4.args,
//Return: nil,
Body: $7.expr,
},
}
}
// `func name(...) <type> { <expr> }`
| FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
fn := &ast.ExprFunc{
Args: $4.args,
Return: $6.typ, // return type is known
Body: $8.expr,
}
isFullyTyped := $6.typ != nil // true if set
m := make(map[string]*types.Type)
ord := []string{}
for _, a := range $4.args {
if a.Type == nil {
// at least one is unknown, can't run SetType...
isFullyTyped = false
break
}
m[a.Name] = a.Type
ord = append(ord, a.Name)
}
if isFullyTyped {
typ := &types.Type{
Kind: types.KindFunc,
Map: m,
Ord: ord,
Out: $6.typ,
}
if err := fn.SetType(typ); err != nil {
// this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err))
}
}
$$.stmt = &ast.StmtFunc{
Name: $2.str,
Func: fn,
}
}
// `class name { <prog> }`
| CLASS_IDENTIFIER colon_identifier OPEN_CURLY prog CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtClass{
Name: $2.str,
Args: nil,
Body: $4.stmt,
}
}
// `class name(<arg>) { <prog> }`
// `class name(<arg>, <arg>) { <prog> }`
| CLASS_IDENTIFIER colon_identifier OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtClass{
Name: $2.str,
Args: $4.args,
Body: $7.stmt,
}
}
// `include name`
| INCLUDE_IDENTIFIER dotted_identifier
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtInclude{
Name: $2.str,
}
}
// `include name(...)`
| INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtInclude{
Name: $2.str,
Args: $4.exprs,
}
}
// `include name as foo`
// TODO: should we support: `include name as *`
| INCLUDE_IDENTIFIER dotted_identifier AS_IDENTIFIER IDENTIFIER
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtInclude{
Name: $2.str,
Alias: $4.str,
}
}
// `include name(...) as foo`
// TODO: should we support: `include name(...) as *`
| INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN AS_IDENTIFIER IDENTIFIER
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtInclude{
Name: $2.str,
Args: $4.exprs,
Alias: $7.str,
}
}
// `import "name"`
| IMPORT_IDENTIFIER STRING
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtImport{
Name: $2.str,
//Alias: "",
}
}
// `import "name" as alias`
| IMPORT_IDENTIFIER STRING AS_IDENTIFIER IDENTIFIER
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtImport{
Name: $2.str,
Alias: $4.str,
}
}
// `import "name" as *`
| IMPORT_IDENTIFIER STRING AS_IDENTIFIER MULTIPLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtImport{
Name: $2.str,
Alias: $4.str,
}
}
/*
// resource bind
| rbind
{
posLast(yylex, yyDollar) // our pos
$$.stmt = $1.stmt
}
*/
;
expr:
BOOL
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprBool{
V: $1.bool,
}
}
| STRING
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprStr{
V: $1.str,
}
}
| INTEGER
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprInt{
V: $1.int,
}
}
| FLOAT
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprFloat{
V: $1.float,
}
}
| list
{
posLast(yylex, yyDollar) // our pos
// TODO: list could be squashed in here directly...
$$.expr = $1.expr
}
| map
{
posLast(yylex, yyDollar) // our pos
// TODO: map could be squashed in here directly...
$$.expr = $1.expr
}
| struct
{
posLast(yylex, yyDollar) // our pos
// TODO: struct could be squashed in here directly...
$$.expr = $1.expr
}
| call
{
posLast(yylex, yyDollar) // our pos
// TODO: call could be squashed in here directly...
$$.expr = $1.expr
}
| var
{
posLast(yylex, yyDollar) // our pos
// TODO: var could be squashed in here directly...
$$.expr = $1.expr
}
| func
{
posLast(yylex, yyDollar) // our pos
// TODO: var could be squashed in here directly...
$$.expr = $1.expr
}
| IF expr OPEN_CURLY expr CLOSE_CURLY ELSE OPEN_CURLY expr CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprIf{
Condition: $2.expr,
ThenBranch: $4.expr,
ElseBranch: $8.expr,
}
}
// parenthesis wrap an expression for precedence
| OPEN_PAREN expr CLOSE_PAREN
{
posLast(yylex, yyDollar) // our pos
$$.expr = $2.expr
}
;
list:
// `[42, 0, -13]`
OPEN_BRACK list_elements CLOSE_BRACK
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprList{
Elements: $2.exprs,
}
}
;
list_elements:
/* end of list */
{
posLast(yylex, yyDollar) // our pos
$$.exprs = []interfaces.Expr{}
}
| list_elements list_element
{
posLast(yylex, yyDollar) // our pos
$$.exprs = append($1.exprs, $2.expr)
}
;
list_element:
expr COMMA
{
posLast(yylex, yyDollar) // our pos
$$.expr = $1.expr
}
;
map:
// `{"hello" => "there", "world" => "big",}`
OPEN_CURLY map_kvs CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprMap{
KVs: $2.mapKVs,
}
}
;
map_kvs:
/* end of list */
{
posLast(yylex, yyDollar) // our pos
$$.mapKVs = []*ast.ExprMapKV{}
}
| map_kvs map_kv
{
posLast(yylex, yyDollar) // our pos
$$.mapKVs = append($1.mapKVs, $2.mapKV)
}
;
map_kv:
expr ROCKET expr COMMA
{
posLast(yylex, yyDollar) // our pos
$$.mapKV = &ast.ExprMapKV{
Key: $1.expr,
Val: $3.expr,
}
}
;
struct:
// `struct{answer => 0, truth => false, hello => "world",}`
STRUCT_IDENTIFIER OPEN_CURLY struct_fields CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprStruct{
Fields: $3.structFields,
}
}
;
struct_fields:
/* end of list */
{
posLast(yylex, yyDollar) // our pos
$$.structFields = []*ast.ExprStructField{}
}
| struct_fields struct_field
{
posLast(yylex, yyDollar) // our pos
$$.structFields = append($1.structFields, $2.structField)
}
;
struct_field:
IDENTIFIER ROCKET expr COMMA
{
posLast(yylex, yyDollar) // our pos
$$.structField = &ast.ExprStructField{
Name: $1.str,
Value: $3.expr,
}
}
;
call:
// fmt.printf(...)
// iter.map(...)
dotted_identifier OPEN_PAREN call_args CLOSE_PAREN
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: $1.str,
Args: $3.exprs,
//Var: false, // default
}
}
// calling a function that's stored in a variable (a lambda)
// `$foo(4, "hey")` # call function value
| dotted_var_identifier OPEN_PAREN call_args CLOSE_PAREN
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: $1.str,
Args: $3.exprs,
// Instead of `Var: true`, we could have added a `$`
// prefix to the Name, but I felt this was more elegant.
Var: true, // lambda
}
}
| expr PLUS expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str, // for PLUS this is a `+` character
},
$1.expr,
$3.expr,
},
}
}
| expr MINUS expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr MULTIPLY expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr DIVIDE expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr EQ expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr NEQ expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr LT expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr GT expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr LTE expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr GTE expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr AND expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| expr OR expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $2.str,
},
$1.expr,
$3.expr,
},
}
}
| NOT expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.OperatorFuncName,
Args: []interfaces.Expr{
&ast.ExprStr{ // operator first
V: $1.str,
},
$2.expr,
},
}
}
// lookup an index in a list or a key in a map
// lookup($foo, $key)
// `$foo[$key]` // no default specifier
| expr OPEN_BRACK expr CLOSE_BRACK
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.LookupFuncName,
Args: []interfaces.Expr{
$1.expr, // the list or map
$3.expr, // the index or key is an expr
//$6.expr, // the default
},
}
}
// lookup an index in a list or a key in a map with a default
// lookup_default($foo, $key, $default)
// `$foo[$key] || "default"`
| expr OPEN_BRACK expr CLOSE_BRACK DEFAULT expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.LookupDefaultFuncName,
Args: []interfaces.Expr{
$1.expr, // the list or map
$3.expr, // the index or key is an expr
$6.expr, // the default
},
}
}
// lookup a field in a struct
// _struct_lookup($foo, "field")
// $foo->field
| expr ARROW IDENTIFIER
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.StructLookupFuncName,
Args: []interfaces.Expr{
$1.expr, // the struct
&ast.ExprStr{
V: $3.str, // the field is always an str
},
//$5.expr, // the default
},
}
}
// lookup a field in a struct with a default
// _struct_lookup_optional($foo, "field", "default")
// $foo->field || "default"
| expr ARROW IDENTIFIER DEFAULT expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.StructLookupOptionalFuncName,
Args: []interfaces.Expr{
$1.expr, // the struct
&ast.ExprStr{
V: $3.str, // the field is always an str
},
$5.expr, // the default
},
}
}
| expr IN expr
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprCall{
Name: funcs.ContainsFuncName,
Args: []interfaces.Expr{
$1.expr,
$3.expr,
},
}
}
;
// list order gets us the position of the arg, but named params would work too!
// this is also used by the include statement when the called class uses args!
call_args:
/* end of list */
{
posLast(yylex, yyDollar) // our pos
$$.exprs = []interfaces.Expr{}
}
// seems that "left recursion" works here... thanks parser generator!
| call_args COMMA expr
{
posLast(yylex, yyDollar) // our pos
$$.exprs = append($1.exprs, $3.expr)
}
| expr
{
posLast(yylex, yyDollar) // our pos
$$.exprs = append([]interfaces.Expr{}, $1.expr)
}
;
var:
dotted_var_identifier
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprVar{
Name: $1.str,
}
}
;
func:
// this is the lambda version, iow, a function as a value (expression)
// `func() { <expr> }`
// `func(<arg>) { <expr> }`
// `func(<arg>, <arg>) { <expr> }`
FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprFunc{
Args: $3.args,
//Return: nil,
Body: $6.expr,
}
}
// `func(...) <type> { <expr> }`
| FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.expr = &ast.ExprFunc{
Args: $3.args,
Return: $5.typ, // return type is known
Body: $7.expr,
}
isFullyTyped := $5.typ != nil // true if set
m := make(map[string]*types.Type)
ord := []string{}
for _, a := range $3.args {
if a.Type == nil {
// at least one is unknown, can't run SetType...
isFullyTyped = false
break
}
m[a.Name] = a.Type
ord = append(ord, a.Name)
}
if isFullyTyped {
typ := &types.Type{
Kind: types.KindFunc,
Map: m,
Ord: ord,
Out: $5.typ,
}
if err := $$.expr.SetType(typ); err != nil {
// this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err))
}
}
}
;
args:
/* end of list */
{
posLast(yylex, yyDollar) // our pos
$$.args = []*interfaces.Arg{}
}
| args COMMA arg
{
posLast(yylex, yyDollar) // our pos
$$.args = append($1.args, $3.arg)
}
| arg
{
posLast(yylex, yyDollar) // our pos
$$.args = append([]*interfaces.Arg{}, $1.arg)
}
;
arg:
// `$x`
var_identifier
{
$$.arg = &interfaces.Arg{
Name: $1.str,
}
}
// `$x <type>`
| var_identifier type
{
$$.arg = &interfaces.Arg{
Name: $1.str,
Type: $2.typ,
}
}
;
bind:
// `$s = "hey"`
var_identifier EQUALS expr
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtBind{
Ident: $1.str,
Value: $3.expr,
}
}
// `$x bool = true`
// `$x int = if true { 42 } else { 13 }`
| var_identifier type EQUALS expr
{
posLast(yylex, yyDollar) // our pos
var expr interfaces.Expr = $4.expr
if err := expr.SetType($2.typ); err != nil {
// this will ultimately cause a parser error to occur...
yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err))
}
$$.stmt = &ast.StmtBind{
Ident: $1.str,
Value: expr,
}
}
;
panic:
// panic("some error")
// generates:
// if panic("some error") {
// _panic "_panic" {} # resource
//}
PANIC_IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN
{
posLast(yylex, yyDollar) // our pos
call := &ast.ExprCall{
Name: $1.str, // the function name
Args: $3.exprs,
//Var: false, // default
}
name := &ast.ExprStr{
V: $1.str, // any constant, non-empty name
}
res := &ast.StmtRes{
Kind: interfaces.PanicResKind,
Name: name,
Contents: []ast.StmtResContents{},
}
$$.stmt = &ast.StmtIf{
Condition: call,
ThenBranch: res,
//ElseBranch: nil,
}
}
;
/* TODO: do we want to include this?
// resource bind
rbind:
var_identifier EQUALS resource
{
posLast(yylex, yyDollar) // our pos
// XXX: this kind of bind is different than the others, because
// it can only really be used for send->recv stuff, eg:
// foo.SomeString -> bar.SomeOtherString
$$.expr = &ast.StmtBind{
Ident: $1.str,
Value: $3.stmt,
}
}
;
*/
resource:
// `file "/tmp/hello" { ... }` or `aws:ec2 "/tmp/hello" { ... }`
colon_identifier expr OPEN_CURLY resource_body CLOSE_CURLY
{
posLast(yylex, yyDollar) // our pos
$$.stmt = &ast.StmtRes{
Kind: $1.str,
Name: $2.expr,