Repository navigation
Expand file tree
/
Copy pathnegative_test.go
More file actions
766 lines (727 loc) · 48.9 KB
/
Copy pathnegative_test.go
File metadata and controls
766 lines (727 loc) · 48.9 KB
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
package parser_test
import (
"strings"
"testing"
"github.com/Open-MBEE/OpenSysML/internal/syntax/parser"
"github.com/Open-MBEE/OpenSysML/internal/syntax/source"
)
// TestNegative verifies parser REJECTS malformed input (Phase 2, Task 2.3)
// Guards against silently accepting garbage
func TestNegative(t *testing.T) {
tests := []struct {
name string
input string
}{
{"unclosed_brace", "part {"},
// The reference form of `include` subsets an existing use case, so it names
// one (SysML.xtext:2300 IncludeUseCaseUsage).
{"include_without_target", "package P { use case def U { include ; } }"},
// A direction prefixes the feature it applies to (SysML.xtext:554
// FeatureDirection).
{"direction_without_feature_action", "package P { action def A { in ; } }"},
{"direction_without_feature_part", "package P { part def D { out ; } }"},
// A word only KerML.xtext spells is a name in SysML, a body or a leading
// multiplicity after it included, so an anonymous KerML declaration is the
// syntax error the pinned pilot reports: `inv { x > 0 }` is
// `no viable alternative at input '>'`, `connector [1] a to b;`
// `no viable alternative at input 'a'`.
{"kerml_inv_body_in_sysml", "package P { part def D { attribute x : ScalarValues::Real; inv { x > 0 } } }"},
{"kerml_connector_multiplicity_in_sysml", "package P { part def D { part a; part b; connector [1] a to b; } }"},
{"empty_requirement", "requirement r { require ; }"},
{"empty_expression", "attribute x = ;"},
// A KerML-only word is a name in SysML, so an operator after it still
// needs its right operand, and `<` opens a comparison rather than a
// short name.
{"kerml_word_name_missing_operand", "part def R { attribute step : Real; assert constraint { step <= } }"},
{"kerml_word_name_unclosed_comparison", "part def R { attribute step : Real; assert constraint { step < } }"},
{"numeric_name", "part def 123;"},
{"missing_semicolon", "part def Engine"},
{"ref_anonymous_unclosed_multiplicity", "package P { ref [1 = x; }"},
// Visibility, direction, `abstract`, `variation` and `constant` alone
// declare no feature; the multiplicity form is the one exception
// (SysML.xtext UsageDeclaration).
{"visibility_without_feature", "part def D { private ; }"},
{"visibility_body_without_feature", "part def D { private { } }"},
{"visibility_value_without_feature", "part def D { private = x; }"},
{"derived_value_without_name", "part def D { derived = x; }"},
{"end_value_without_name", "part def D { end = x; }"},
// `composite` or `portion` closes the feature prefix: no other feature
// modifier may follow it (SysML.xtext BasicFeaturePrefix).
{"composite_ref_decl", "part def D { composite ref; }"},
{"composite_ref_body", "part def D { composite ref { } }"},
{"portion_ref_body", "part def D { portion ref { } }"},
{"composite_readonly", "part def D { composite readonly; }"},
{"composite_derived", "part def D { composite derived x; }"},
{"composite_visibility", "part def D { composite private x; }"},
{"composite_direction", "part def D { composite in x; }"},
{"composite_abstract", "part def D { composite abstract x; }"},
{"invalid_keyword_combo", "def usage MyPart;"},
{"incomplete_connection", "connector c connect a"},
// A connector end is a feature chain (SysML BNF 8.2.2.13.1), never an
// expression: `[…]` and `->` continue it into one, as does a `#(…)`
// anywhere but whole at the end of the chain (the indexed-end extension).
{"connect_end_element_selection_chain", "part def A { part s; part k; connect [1] s.y#(1).v to [1] k.u; }"},
{"connect_end_empty_element_selection", "part def A { part s; part k; connect [1] s.y#() to [1] k.u; }"},
{"connect_end_index", "part def A { part s; part k; connect [1] s.y[1] to [1] k.u; }"},
{"connect_end_invocation", "part def A { part s; part k; connect [1] s.y->first to [1] k.u; }"},
{"unterminated_string", `part p { doc /* comment `},
{"unterminated_string_literal", "package P {\n attribute s = \"open\n attribute t = 1;\n}"},
{"unterminated_unrestricted_name", "package P {\n part def 'A;\n part def B;\n}"},
{"double_colon_only", "attribute ::x;"},
// A declaration opening with a specialization states no name
// (SysML.xtext FeatureDeclaration), and its MultiplicityPart puts the
// bracketed multiplicity ahead of `ordered`/`nonunique`, once each.
{"name_after_specialization_part", "part def D { :>> values ordered x; }"},
{"short_name_after_specialization_part", "part def D { :>> values <s> ordered; }"},
{"duplicate_ordered_after_specialization", "part def D { :>> values ordered ordered; }"},
{"multiplicity_after_ordered", "part def D { :>> values ordered [2]; }"},
// A classifier declaration admits one specialization list.
{"def_two_specialization_lists", "part def X :> A :> B;"},
// Behavioral negatives (Phase B1.2)
{"state_entry_no_keyword", "state s { entry }"},
{"action_dangling_fork", "action a { fork }"},
{"transition_then_only", "transition first then"},
{"named_final_node", "action a { done finish; }"},
// InitialNodeMember is an ActionBodyItem production alone (SysML.xtext:1376):
// a one-ended `first` outside an action body names no target.
{"one_ended_first_in_part_body", "part def P { part a; first a; }"},
{"one_ended_first_in_state_body", "state def M { state a; first a; }"},
{"one_ended_first_in_succession_body", "action def A { action p; action s; first p then s { action o; first o; } }"},
{"one_ended_first_in_initial_node_body", "action def A { action a; action b; first a { first b; } }"},
// RequirementConstraintMember belongs to a RequirementBody (SysML.xtext:2039):
// `assume`/`require` declare nothing anywhere else.
{"require_outside_requirement_body", "part def P { attribute size; require constraint { size >= 1 } }"},
{"assume_outside_requirement_body", "part def P { attribute size; assume constraint { size >= 1 } }"},
{"named_final_keyword", "action a { final finish; }"},
{"two_ended_then", "action a { action start; action finish; then start finish; }"},
{"state_member_then", "state s { state start; state finish; start then finish; }"},
{"requirement_empty_require", "requirement r { require }"},
{"calc_empty_return", "calc c { return }"},
// A specialization after `return` names what the result subsets.
{"calc_return_subsets_no_target", "calc def C { return :> = 1; }"},
{"calc_return_named_subsets_no_target", "calc def C { return r :> = 1; }"},
{"calc_return_short_name_unclosed", "calc def C { return <r result : Real = 1; }"},
{"calc_return_short_name_empty", "calc def C { return <> = 1; }"},
// A result declares something: a value or a body alone is no FeatureDeclaration.
{"calc_return_value_only", "calc def C { in n : Real; return = n; }"},
{"calc_return_body_only", "calc def C { return { doc /* r */ } }"},
{"calc_return_multiplicity_unclosed", "calc def C { in xs : Real[*]; return [* = xs; }"},
// A short name must be closed before the member's declaration tail.
{"calc_short_name_unclosed", "calc def C { <s x = 1; }"},
{"calc_short_name_empty", "calc def C { <> x = 1; }"},
{"part_short_name_unclosed", "part def P { <s :> x = 1; }"},
{"calc_while_no_condition", "calc def C { while { i = i + 1; } }"},
{"calc_while_unclosed_body", "calc def C { while i < 2 { i = i + 1; }"},
{"calc_for_no_variable", "calc def C { for in xs { } }"},
{"calc_assignment_no_value", "calc def C { i = ; }"},
// A terminate statement ends with `;` and names at most one target.
{"terminate_unterminated", "action def A { action a { terminate assign n := 1; } }"},
{"terminate_target_incomplete", "action def A { action a { terminate a::; } }"},
{"calc_if_no_body", "calc def C { if i < 2 }"},
{"constraint_incomplete", "constraint c { assert }"},
// A parameterised constraint body asserts conditions like any other, so a
// condition that is missing its expression is an error there too.
{"constraint_params_assert_no_condition", "constraint c { in x : Real; assert; }"},
{"constraint_params_assume_no_condition", "constraint c { in x : Real; assume }"},
{"constraint_params_assert_not_no_condition", "constraint c { in x : Real; assert not; }"},
// A require member referencing a requirement takes specializations and a
// body, so neither may be left half written.
{"require_reference_no_specialization_type", "requirement r { require Q::x : ; }"},
{"require_reference_unclosed_body", "requirement r { require Q::x { require constraint { x > 0 } }"},
// Prefix metadata needs a name, and the declaration after it a terminator.
{"require_prefix_metadata_no_type", "requirement r { require #; }"},
{"require_prefix_metadata_unterminated", "requirement r { require #goal c }"},
// Prefix metadata belongs after these keywords, not ahead of them.
{"subject_prefix_metadata_before_keyword", "requirement r { #goal subject s : T; }"},
{"subject_prefix_metadata_before_keyword_eof", "requirement r { #goal subject"},
{"actor_prefix_metadata_before_keyword", "requirement r { #goal actor a; }"},
{"variant_prefix_metadata_before_keyword", "variation part def V { #goal variant part v; }"},
{"assume_prefix_metadata_before_keyword", "requirement r { #goal assume constraint a : C; }"},
{"require_prefix_metadata_before_keyword", "requirement r { #goal require constraint r : C; }"},
{"require_prefix_metadata_before_keyword_bare", "requirement r { #goal require ; }"},
{"verify_prefix_metadata_before_keyword", "verification def V { #goal verify requirement r; }"},
{"frame_prefix_metadata_before_keyword", "requirement def R { #goal frame concern c; }"},
{"render_prefix_metadata_before_keyword", "view def V { #goal render rendering r; }"},
// An assertion's prefix metadata comes ahead of `assert`, not after it or
// its `not` (SysML.xtext AssertConstraintUsage `OccurrenceUsagePrefix 'assert'`).
{"assert_prefix_metadata_after_keyword", "package P { part def D { assert #B constraint c; } }"},
{"assert_prefix_metadata_after_not", "package P { part def D { assert not #B constraint c; } }"},
// A target succession's body closes, and its ends are still required
// (SysML.xtext:1698 ActionTargetSuccession).
{"succession_body_unclosed", "package P { action def A { action a; first a; then a { doc /* x */ } }"},
{"succession_body_no_target", "package P { action def A { action a; first a; then { } } }"},
// Only an action body item reaches ActionTargetSuccession (SysML.xtext:1698), so a
// bodied `then` is not a state, definition or namespace member (:1796-1801, :516-524).
{"entry_succession_body", "package P { state def S { state starting; entry; then starting { doc /* x */ } } }"},
{"definition_succession_body", "package P { part def D { part a; part b; then b { doc /* x */ } } }"},
{"namespace_succession_body", "package P { action a; action b; then b { doc /* x */ } }"},
// A control node in a case body takes a declaration or nothing, and its
// body closes (SysML.xtext:1676 JoinNode, :1682 ForkNode).
{"case_body_fork_unclosed", "package P { use case def U { fork f { action a; } }"},
{"case_body_join_no_terminator", "package P { use case def U { join j } }"},
// A `for` in a case body states a variable and a sequence
// (SysML.xtext ForLoopNode).
{"case_body_for_no_variable", "package P { analysis def A { for in xs { } } }"},
{"case_body_for_no_sequence", "package P { analysis def A { for i in { } } }"},
// A metadata usage's `about` names at least one annotated element
// (SysML.xtext:145-147).
{"metadata_about_no_target", "package P { metadata def M; @M about ; }"},
{"metadata_about_trailing_comma", "package P { metadata def M; part a; @M about a, ; }"},
// Each one is a qualified name, not a feature chain (SysML.xtext AnnotatedElement).
{"metadata_usage_about_feature_chain", "package P { metadata def M; part a { attribute x; } metadata m : M about a.x; }"},
{"metadata_usage_about_no_target", "package P { metadata def M; metadata m : M about ; }"},
// A prefixed dependency still states both of its ends
// (SysML.xtext:55-58).
{"prefixed_dependency_no_supplier", "package P { metadata def M; part a; #M dependency d from a to ; }"},
// A verification's requirement reference is a name, chained or not
// (SysML.xtext:2119).
{"verify_chained_no_member", "package P { verification def V { verify a. ; } }"},
// `not` negates a satisfaction, and nothing else at member level.
{"not_without_satisfy", "package P { not r1 by p; }"},
{"not_satisfy_no_subject", "package P { not satisfy r1 by ; }"},
// A guarded succession needs a guard expression and a target after it.
{"guarded_succession_no_guard", "action def A { action a; action b; succession S first a if then b; }"},
{"guarded_succession_no_target", "action def A { action a; succession S first a if x == 0 then ; }"},
// Orthogonality is spelled `parallel` before a state body, so a bodied
// `region` member is not a state body item.
{"region_member", "state def S { region r { state a; } }"},
{"state_fork_no_name", "state s { fork ; }"},
{"state_join_no_semicolon", "state s { join sync state t; }"},
{"call_trigger_unclosed_params", "state s { accept op(a then t; }"},
{"call_trigger_missing_param_name", "state s { accept op(,) then t; }"},
{"perform_no_reference", "action a { perform ; }"},
{"perform_dangling_chain", "action a { perform b.; }"},
// A connector clause whose first end is missing reports the gap at the
// `to`/`then` delimiter, not the delimiter misread as the end's name.
{"connection_first_end_missing", "part def C { connection c : I connect to ; }"},
{"then_no_target", "action a { then; }"},
{"nested_source_multiplicity_then_no_target", "action def A { if true { action a; [1] then ; } }"},
{"satisfy_dangling_by", "requirement r { assert satisfy x by; }"},
{"allocate_missing_target", "package q { allocate a to ; }"},
// `allocate` is one keyword with one role, and it must be followed by a
// ConnectorPart (D1, SysML.xtext:1219-1222).
{"allocate_without_connector_part", "package q { allocate al; }"},
{"allocate_with_body_and_no_ends", "package q { allocate al { } }"},
{"allocate_def", "package q { allocate def D; }"},
{"message_payload_declaration_no_type", "message m of pay : from a to b;"},
{"message_payload_declaration_no_target", "message m of pay : T from a;"},
// `state s { defer ; }` and `state s { history ; }` are not malformed:
// neither word is a grammar literal, so each names a reference usage there
// (docs/reference/grammar/conformance-audit.md). `defer <event>;` is the
// removed OpenSysML deferral extension, reported as such.
{"defer_member_removed", "state s { defer Ping; }"},
{"defer_member_removed_list", "state s { defer Ping, setSpeed(value); }"},
{"defer_member_removed_in_def", "state def S { state busy { defer Ping; } }"},
{"defer_no_semicolon", "state s { defer Ping state t; }"},
{"defer_trailing_comma", "state s { defer Ping, ; }"},
{"deep_without_history", "state s { deep resume; }"},
{"shallow_without_history", "state s { shallow resume; }"},
{"history_no_semicolon", "state s { history resume state t; }"},
// `entry point;` is not malformed: `point` is not reserved, so it is an
// entry action referencing a feature named `point`. Dropping the ';' is.
{"entry_reference_no_semicolon_brace", "state s { entry point }"},
// There is no entry or exit point pseudostate: `entry point x;` is an entry
// action referencing `point`, followed by a stray name.
{"entry_point_pseudostate", "state s { entry point x; }"},
{"exit_point_pseudostate", "state s { exit point x; }"},
{"entry_point_pseudostate_in_def", "state def S { entry point into; exit point outOf; }"},
{"entry_reference_no_semicolon", "state s { entry warmUp state t; }"},
{"exit_reference_no_semicolon", "state s { exit coolDown state t; }"},
{"do_reference_dangling_chain", "state s { do warmUp.; }"},
// `on` and `var` are names, not keywords, so genuine misuse around them
// is still reported: a state named `on` without its `;`, a transition
// whose trigger is missing after a source named `on`, and a `var`-marked
// declaration with no type. A `var` prefix with the kind keyword left out
// (KerML BasicFeaturePrefix, `var a : Integer;`) is not supported and is
// reported rather than read as a reference to a feature named `var`.
{"state_named_on_no_semicolon", "state def S { state on }"},
{"transition_from_on_no_trigger", "state def S { state on; transition first on accept then off; }"},
{"var_prefixed_declaration_no_type", "part def D { var attribute x : ; }"},
{"attribute_named_var_no_type", "part def D { attribute var : ; }"},
{"var_prefix_without_kind_keyword", "calc def C { var a : Integer; }"},
{"end_no_feature", "connection def C { end ; }"},
{"end_unclosed_multiplicity", "connection def C { end [1 part bead : T; }"},
{"connector_end_no_reference_target", "part p { connection : C connect bead references to rim; }"},
{"flow_source_without_target", "part def C { item Fuel; part a; flow f of Fuel from a; }"},
{"nary_connect_unclosed", "part def C { part a; part b; connection conn connect (a, b; }"},
{"nary_connect_trailing_comma", "part def C { part a; part b; connection conn connect (a, b, ); }"},
{"nary_connect_empty", "part def C { connection conn connect (); }"},
{"anonymous_nary_connect_unclosed", "part def C { part a; part b; connect (a, b; }"},
{"anonymous_nary_connect_empty", "part def C { connect (); }"},
// Occurrence modifiers (`individual`, `snapshot`) on a usage.
// `individual ;` is well-formed (SysML.xtext Usage: UsageDeclaration?) and is
// covered by TestParseUsageOccurrenceModifiers instead.
{"individual_usage_no_type", "individual testSystem : ;"},
{"individual_usage_no_body", "individual testSystem : TestSystem"},
{"snapshot_usage_no_type", "snapshot occurrence takeoff : ;"},
{"individual_parameter_no_type", "action a { in individual v : ; }"},
// A keyword names a parameter, so the keywords that state something else
// there are still read as that: a missing value or type is an error
// rather than a parameter so named.
{"keyword_named_parameter_no_type", "action a { in 'type' : ; }"},
{"parameter_default_no_value", "action a { in x default ; }"},
{"parameter_redefines_no_target", "action a { in redefines ; }"},
// A direction before a usage kind declares that usage (SysML.xtext
// RefPrefix), so its declaration is held to the same shape.
{"directed_concern_no_type", "action a { in concern c : ; }"},
{"directed_use_case_no_type", "action a { out use case u : ; }"},
{"directed_requirement_unclosed_body", "action a { out requirement r : R { }"},
// A member-attached `then` sequences the members either side of it, so
// a body with nothing on one side, or a member the notation does not
// allow one before, declares no order and is rejected rather than
// parsed with the keyword dropped. A `then` beside a member with no
// name is legal notation, bound by position
// (TestSuccessionBindsUnnamedEndsByPosition).
{"leading_then_has_no_source", "action a { then action b; }"},
{"trailing_then_has_no_target", "action a { action b; then }"},
{"then_then", "action a { action b; then then action c; }"},
{"then_before_definition", "action a { action b; then action def C; }"},
{"then_before_package", "part def P { part a; then package Inner { } }"},
{"then_before_attribute", "part def P { part a; then attribute x; }"},
{"then_before_import", "part def P { part a; then import Other::*; }"},
// A feature specialization keyword after a short name states a
// relationship, so a missing target is an error rather than a name.
{"short_name_redefines_no_target", "part p { attribute <sn> redefines; }"},
{"short_name_redefines_symbol_no_target", "part p { attribute <sn> :>>; }"},
{"short_name_defined_by_no_type", "part p { attribute <sn> defined by ; }"},
// A requirement's subject, assume and require members take an
// identification (SysML.xtext SubjectUsage, RequirementConstraintUsage →
// UsageDeclaration), so a malformed short name is an error there too.
{"subject_empty_short_name", "requirement def R { subject <> x; }"},
{"subject_unclosed_short_name", "requirement def R { subject <s x; }"},
{"assume_empty_short_name", "requirement def R { assume constraint <> c; }"},
{"assume_unclosed_short_name", "requirement def R { assume constraint <c x; }"},
{"require_empty_short_name", "requirement def R { require constraint <> c; }"},
{"require_unclosed_short_name", "requirement def R { require constraint <c x; }"},
// The notation has no definition of a rendering a view names, of a
// concern a body frames, or of a stakeholder or actor: those keywords own
// a usage, not a definition (SysML.xtext ViewRenderingUsage,
// FramedConcernUsage, StakeholderUsage, ActorUsage). `render ;` and
// `frame ;` are absent deliberately: `frame` and `render` are legal
// names, so those declare a feature so named rather than being errors.
{"render_definition", "view def V { render def R; }"},
{"frame_definition", "viewpoint def V { frame def C; }"},
{"stakeholder_definition", "stakeholder def Reviewer;"},
{"actor_definition", "actor def Operator;"},
{"stakeholder_no_declaration", "viewpoint def V { stakeholder ; }"},
{"actor_no_declaration", "requirement def R { actor ; }"},
// A rendering reference takes no value: ViewRenderingUsage has no
// ValuePart, unlike the performed action reference that shares its shape.
{"render_reference_value", "view def V { render r = 3; }"},
// FramedConcernMember is a RequirementBodyItem, ElementFilterMember a
// package/view-body item, and AcceptNode an ActionBodyItem (SysML.xtext):
// each is rejected by the bodies its grammar omits.
{"frame_in_view_body", "view V { frame concern c : C; }"},
{"frame_ref_in_view_body", "view V { frame c; }"},
{"frame_in_view_def_body", "view def V { frame concern c : C; }"},
{"frame_in_rendering_body", "rendering def R { frame c; }"},
{"frame_in_part_def_body", "part def P { frame c; }"},
{"filter_in_viewpoint_def_body", "viewpoint def V { filter true; }"},
{"filter_in_rendering_body", "rendering def R { filter true; }"},
{"filter_in_part_def_body", "part def P { filter true; }"},
{"accept_in_view_body", "view V { accept S; }"},
{"accept_action_in_view_body", "view V { action a accept S; }"},
{"accept_in_part_def_body", "part def P { accept S; }"},
// The sequence index and the collection notations: `#` indexes through a
// parenthesized index and `.?` selects through a body, so each is
// rejected where the notation it needs is absent rather than parsed as
// the operand alone.
{"index_no_paren", "attribute x = xs#3;"},
{"index_no_index", "attribute x = xs#();"},
{"index_unclosed", "attribute x = xs#(1;"},
{"index_bracket_unclosed", "attribute x = 5 [m;"},
{"index_bracket_empty", "attribute x = 5 [];"},
{"select_no_body", "attribute x = xs.?;"},
{"select_expression_body", "attribute x = xs.? x > 1;"},
{"select_unclosed_body", "attribute x = xs.?{in x; x > 1;"},
{"collect_unclosed_body", "attribute x = xs.{in x; x * 2;"},
{"body_param_no_name", "attribute x = xs.{in ; 1};"},
{"body_param_no_type", "attribute x = xs.{in y : ; 1};"},
{"receiver_no_operation", "attribute x = xs->;"},
{"receiver_unclosed_args", "attribute x = xs->union((1, 2);"},
// A conjugation names the definition it conjugates, only an interface end
// may omit its declaration, a required requirement binds a feature in its
// body, and a portion usage declares what it portions.
{"conjugated_no_type", "part def P { port p : ~; }"},
{"conjugated_no_type_after_name", "port def P; port p ~;"},
{"conjugated_end_no_type", "connection def C { end e : ~; }"},
{"end_outside_connector", "part def P { end ; }"},
{"end_outside_connector_package", "package p { end ; }"},
{"end_in_package_nested_in_interface", "interface def I { package P { end ; } }"},
{"end_in_satisfy_nested_in_interface", "interface def I { satisfy requirement r { end ; } }"},
{"end_in_binding_nested_in_interface", "interface def I { binding b { end ; } }"},
{"require_qualified_malformed_body", "analysis def A { objective o { require Q::r { :>> ; } } }"},
{"require_qualified_trailing_colons", "analysis def A { objective o { require Q::; } }"},
{"timeslice_no_subject", "package p { timeslice :>> ; }"},
{"timeslice_usage_no_type", "package p { timeslice item i : ; }"},
{"timeslice_unterminated", "package p { timeslice item i"},
// `variation` and `variant` qualify a declaration, so each is rejected
// where the declaration it qualifies is absent or malformed.
{"variation_no_declaration", "variation ;"},
{"variation_attribute_no_name", "part p { variation attribute : ; }"},
{"variant_unclosed_body", "part p { variation attribute cut { variant attribute cutIdeal { :>> cost = 1.0; } }"},
{"variant_selection_no_variant_name", "part p { attribute :>> cut = cut::; }"},
// Behavioral notation: a flow states both of its ends, an accept its
// payload, a loop the condition its `until` promises and a succession a
// target, so each is reported where one is missing rather than read as
// the shorter form it is not.
{"flow_from_without_to", "action def A { action a; flow x from a; }"},
{"flow_named_from_no_source", "action def A { flow x from to b; }"},
{"accept_when_no_condition", "action def A { accept when; }"},
{"prefixed_accept_no_payload", "action def A { metadata def M; #M action a accept ; }"},
{"prefixed_accept_no_terminator", "action def A { metadata def M; #M action a accept e : E }"},
{"accept_at_no_instant", "action def A { accept at; }"},
{"accept_no_payload", "action def A { accept; }"},
{"accept_subsets_no_event", "action def A { action i accept :>; }"},
{"loop_until_no_condition", "action def A { loop action { } until; }"},
{"loop_until_no_semicolon", "action def A { action b; then loop action { } until x }"},
{"then_done_no_semicolon", "action def A { action b; then done }"},
// A control-node keyword followed by a name declares a node, whatever the
// body declares, so a final node given a name and a node given two are
// diagnosed as declarations rather than read as edges to the member.
{"then_done_named_beside_declared_done", "action def A { action done; action b; then done D; }"},
{"then_fork_two_names_beside_declared_fork", "action def A { action fork; action b; then fork F G; }"},
{"send_via_no_port", "action def A { send Data() via; }"},
{"send_no_target", "action def A { send Data() to; }"},
{"decision_else_no_target", "action def A { action m; first m; then decide; else; }"},
{"transition_trigger_no_target", "state def S { state a; transition first a accept Ping; }"},
{"transition_two_triggers", "state def S { state a; state b; transition first a accept Ping accept Pong then b; }"},
{"transition_two_targets", "state def S { state a; state b; transition first a then b then b; }"},
{"transition_do_without_action", "state def S { state a; state b; transition first a do then b; }"},
{"exhibit_state_unclosed_body", "part def P { exhibit state modes { state off; }"},
{"namespace_succession_no_target", "package Q { part p; first p then; }"},
// Only a transition effect is closed by the transition's next clause, so
// a statement in an action body still needs its ';'.
{"body_assignment_no_semicolon", "action def A { attribute x; action b; assign x := 1 then b; }"},
// An assignment target names a feature or ends in one (SysML.xtext
// TargetParameter), so a literal, an operator or an invocation is not one.
{"assignment_target_literal", "action def A { assign 1 := 2; }"},
{"assignment_target_operator", "action def A { attribute x; assign x + 1 := 2; }"},
{"assignment_target_invocation", "action def A { calc def f { return : Integer = 1; } assign f() := 2; }"},
{"assignment_target_index", "action def A { attribute xs : Integer[0..*]; assign xs[1] := 2; }"},
{"body_send_no_semicolon", "action def A { action b; part self; send Data() to self then b; }"},
// A transition takes exactly one ';', which its effect statement shares
// (SysML.xtext TransitionUsage ends with ActionBody); a second one is not
// an empty member.
{"transition_effect_perform_two_semicolons", "state def S { state a; state b; transition first a do perform Bump then b ;; }"},
{"transition_effect_assign_two_semicolons", "state def S { attribute x; state a; state b; transition first a do assign x := 1 then b ;; }"},
{"transition_effect_no_semicolon", "state def S { attribute x; state a; state b; transition first a do assign x := 1 then b }"},
{"transition_braced_effect_no_semicolon", "state def S { attribute x; state a; state b; transition first a do { assign x := 1; } then b }"},
// A braced state behavior block is one anonymous action's body, so it
// closes, its statements end, and it holds action body items only.
{"entry_block_unterminated", "state def S { attribute x; state a { entry { assign x := 1; } }"},
{"do_block_unterminated", "state def S { attribute x; state a { do { assign x := 1; }"},
{"exit_block_unterminated", "state def S { attribute x; state a { exit { assign x := 1; "},
{"transition_effect_block_unterminated", "state def S { attribute x; state a; state b; transition first a do { assign x := 1; then b; }"},
{"entry_block_terminate_unterminated", "state def S { state a { entry { terminate } } }"},
{"do_block_terminate_unterminated", "state def S { state a { do { terminate assign x := 1; } } }"},
{"transition_effect_block_terminate_unterminated", "state def S { state a; state b; transition first a do { terminate } then b; }"},
{"entry_block_expression_statement", "state def S { attribute x; state a { entry { x + 1; } } }"},
{"do_block_stray_token", "state def S { state a { do { ) } } }"},
{"exit_block_assign_no_value", "state def S { attribute x; state a { exit { assign x := ; } } }"},
{"transition_effect_block_stray_token", "state def S { state a; state b; transition first a do { ] } then b; }"},
// A binding end names a feature by a qualified name or a chain of them,
// so neither qualification nor chaining may end in nothing.
{"binding_end_qualification_no_name", "package P { part c; binding bind R:: = c; }"},
{"binding_end_chain_trailing_dot", "package P { part a; part c; binding bind a. = c; }"},
{"binding_end_chain_trailing_dot_qualified", "package P { part c; binding bind R::a. = c; }"},
{"binding_end_unterminated", "package P { part a; binding bind R::a }"},
{"binding_end_no_target", "package P { part a; binding bind R::a = ; }"},
// An expression is not a ConnectorEndMember, so a binding whose right
// side is one is rejected (see TestBindingEndFailuresAreDistinguishable).
{"binding_end_expression", "package P { part def D { attribute a; attribute b; bind a = b * 2; } }"},
// A named binding end references a feature after its `::>` (SysML.xtext
// ConnectorEnd), and a binding joins exactly two ends with '=', never a
// succession's `then`.
{"binding_named_end_no_target", "package P { part def D { attribute a; attribute b; bind e1 ::> = b; } }"},
{"binding_named_second_end_no_target", "package P { part def D { attribute a; attribute b; bind a = e2 references ; } }"},
{"binding_named_end_no_eq", "package P { part def D { attribute a; attribute b; bind e1 ::> a b; } }"},
{"binding_ends_then", "package P { part def D { attribute a; attribute b; attribute c; bind a = b then c; } }"},
{"binding_named_end_expression", "package P { part def D { attribute a; attribute b; bind e1 ::> a = b + 1; } }"},
{"binding_declaration_bind_no_ends", "package P { part def D { attribute a; binding x : AB bind ; } }"},
// A SysML UsageDeclaration puts its multiplicity after the name; a bracket
// before the name is a connector end's (pilot: `no viable alternative at input 'ab2'`).
{"binding_multiplicity_before_name", "package P { part def D { attribute a; attribute b; binding [1] ab2 : AB bind a = b; } }"},
// A connection, interface or flow usage stating its ends where its name
// would go still states both ends and closes the body it opens.
{"connect_no_ends", "part def P { connect; }"},
{"connect_body_no_ends", "part def P { connect { attribute x; } }"},
{"connect_body_unclosed", "part def P { part a; part b; connect a to b { attribute x; }"},
{"connect_to_no_target_before_body", "part def P { part a; connect a to { } }"},
{"interface_ends_no_target", "package P { part a; interface a.p to ; }"},
{"interface_ends_no_to", "package P { part a; part b; interface a.p b.p; }"},
{"interface_ends_unclosed_body", "package P { part a; part b; interface a.p to b.p { attribute x; }"},
{"flow_ends_no_target_before_body", "action def A { action a; flow a.x to { } }"},
{"flow_ends_unclosed_body", "action def A { action a; action b; flow a.x to b.x { attribute y; }"},
// An accept node standing as a statement states its payload and, where it
// names one, the port it accepts through.
{"accept_statement_no_payload", "action def A { loop { accept; } }"},
{"accept_statement_no_payload_type", "action def A { loop { accept e : ; } }"},
{"accept_statement_via_no_port", "action def A { loop { accept e : E via; } }"},
{"then_accept_no_payload", "action def A { action b; then accept; }"},
// A word SysML.xtext does not reserve names a usage, so a declaration so
// named that is malformed is still reported rather than read as the
// KerML relationship the same word states elsewhere.
{"part_named_chains_no_type", "part chains : ;"},
{"part_named_differences_no_terminator", "part differences"},
{"part_named_disjoint_no_type", "part disjoint : ;"},
{"part_named_type_no_type", "part type : ;"},
// The relationship spelling still states the relationship where one
// belongs, so its missing operand is still an error.
{"usage_disjoint_from_no_target", "part def T { part a; part b disjoint from ; }"},
{"usage_inverse_of_no_target", "part def T { part a; part b inverse of ; }"},
{"usage_chains_no_target", "part def T { part a; part b chains ; }"},
{"usage_featured_by_no_target", "part def T { part b featured by ; }"},
// The generalized usage declaration widening keeps its neighbouring
// malformed spellings reported (F66).
{"ref_redefines_no_target", "part def T { ref redefines [4]; }"},
{"ref_redefines_unclosed_multiplicity", "part def T { ref redefines x[4; }"},
{"verify_redefines_no_target", "requirement def R { verify r :>> ; }"},
{"variant_use_case_no_type", "use case def U { variant use case uc : ; }"},
{"assert_not_no_condition", "part def T { assert not ; }"},
{"assert_not_no_body_end", "part def T { assert not c { }"},
{"assert_global_name_missing", "package P { assert $; }"},
{"assert_global_scope_missing_name", "package P { assert $::; }"},
// `frame` and `render` are SysML keywords, so a framing or rendering
// with no reference is reported rather than read as a name.
{"frame_no_concern", "viewpoint def V { frame; }"},
{"frame_concern_no_declaration", "viewpoint def V { frame concern }"},
{"render_no_rendering", "view def V { render; }"},
// A name follows its kind keyword, never precedes it (SysML.xtext
// DefinitionDeclaration, UsageDeclaration).
{"name_before_usage_keyword", "part def B { foo attribute bar : A; }"},
{"name_before_definition_keyword", "package P { x part def Q; }"},
// A specialization following a parameter's multiplicity still names its
// target and closes its multiplicity (KerML.xtext:574).
{"parameter_multiplicity_redefines_no_target", "action def A { in x : Integer[1] redefines ; }"},
{"parameter_multiplicity_redefines_symbol_no_target", "action def A { in x : Integer[1] :>> ; }"},
{"parameter_multiplicity_ordered_subsets_no_target", "action def A { in xs : Integer[*] ordered :> ; }"},
{"parameter_multiplicity_unclosed_before_redefines", "action def A { in x : Integer[1 redefines A::x; }"},
{"parameter_multiplicity_redefines_no_terminator", "action def A { in x : Integer[1] redefines A::x }"},
{"out_parameter_multiplicity_redefines_no_target", "action def A { out x : Integer[1] redefines ; }"},
{"inout_parameter_multiplicity_redefines_no_target", "action def A { inout x : Integer[1] :>> ; }"},
{"return_multiplicity_redefines_no_target", "calc def C { return : Integer[1] ordered :>> ; }"},
// A sequence expression admits one trailing comma after an operand
// (KerMLExpressions.xtext SequenceExpression); an argument list admits
// none (PositionalArgumentList, NamedArgumentList).
{"sequence_two_trailing_commas", "package P { attribute s = (1, 2,,); }"},
{"sequence_comma_only", "package P { attribute s = (,); }"},
{"sequence_index_comma_only", "package P { attribute s = (1, 2); attribute e = s#(,); }"},
{"argument_list_trailing_comma", "package P { attribute s = f(1,); }"},
{"named_argument_list_trailing_comma", "package P { attribute s = f(x = 1,); }"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sf := source.New(tt.name+".sysml", []byte(tt.input))
p := parser.New(sf)
_ = p.ParseFile()
if len(p.Diagnostics) == 0 {
t.Errorf("Expected parse errors for malformed input, got none.\nInput: %s", tt.input)
}
})
}
}
func TestActionParameterPrefixModifierOrder(t *testing.T) {
contexts := []struct {
name, action string
}{
{"action-def", "action def A { PARAM }"},
{"nested-action", "action def A { action n { PARAM } }"},
{"loop", "action def A { loop { PARAM } until true; }"},
{"for", "action def A { for i in (1, 2) { PARAM } }"},
{"if-then", "action def A { if true { PARAM } }"},
{"if-else", "action def A { if true { } else { PARAM } }"},
}
modifiers := []string{"derived", "abstract", "variation", "constant"}
orderMessage := func(keyword, earlier string) string {
return "`" + keyword + "` must come before `" + earlier +
"`: a usage prefix is written direction, `derived`, `abstract` or `variation`, `constant`, then `ref` (SysML.xtext RefPrefix, BasicUsagePrefix)"
}
assertOrderingDiagnostic := func(t *testing.T, name, action, parameter, keyword, earlier string) {
t.Helper()
input := "package P { attribute def T; " +
strings.Replace(action, "PARAM", parameter, 1) + " }"
p := parser.New(source.New(name+".sysml", []byte(input)))
_ = p.ParseFile()
want := orderMessage(keyword, earlier)
var matches int
for _, diagnostic := range p.Diagnostics {
if strings.Contains(diagnostic.Message, "must come before") {
if diagnostic.Message != want {
t.Fatalf("ordering diagnostic = %q, want %q", diagnostic.Message, want)
}
matches++
}
}
if matches != 1 {
t.Fatalf("got %d ordering diagnostics, want one %q; all diagnostics: %v", matches, want, p.Diagnostics)
}
}
for _, context := range contexts {
for _, modifier := range modifiers {
t.Run(context.name+"/ref-first/"+modifier, func(t *testing.T) {
assertOrderingDiagnostic(t, context.name+"-"+modifier,
context.action, "in ref "+modifier+" p : T;", modifier, "ref")
})
}
}
misordered := []struct {
name, parameter, keyword, earlier string
}{
{"constant-derived", "in constant derived p : T;", "derived", "constant"},
{"abstract-derived", "in abstract derived p : T;", "derived", "abstract"},
{"constant-abstract", "in constant abstract p : T;", "abstract", "constant"},
{"constant-variation", "in constant variation p : T;", "variation", "constant"},
}
for _, context := range contexts {
for _, form := range misordered {
t.Run(context.name+"/misordered/"+form.name, func(t *testing.T) {
assertOrderingDiagnostic(t, context.name+"-"+form.name,
context.action, form.parameter, form.keyword, form.earlier)
})
}
}
for _, occurrence := range []string{"individual", "snapshot", "timeslice", "event"} {
t.Run("occurrence-before-derived/"+occurrence, func(t *testing.T) {
assertOrderingDiagnostic(t, "occurrence-"+occurrence,
contexts[0].action, "in "+occurrence+" derived p : T;", "derived", occurrence)
})
}
for _, parameter := range []string{
"in abstract variation p : T;",
"in derived derived p : T;",
} {
t.Run("equal-rank/"+strings.ReplaceAll(parameter, " ", "-"), func(t *testing.T) {
input := "package P { attribute def T; " +
strings.Replace(contexts[0].action, "PARAM", parameter, 1) + " }"
p := parser.New(source.New("equal-rank.sysml", []byte(input)))
_ = p.ParseFile()
if len(p.Diagnostics) == 0 {
t.Fatal("equal-rank prefixes were accepted without the existing prefix diagnostic")
}
for _, diagnostic := range p.Diagnostics {
if strings.Contains(diagnostic.Message, "must come before") {
t.Fatalf("equal-rank prefixes got an ordering diagnostic: %q", diagnostic.Message)
}
}
})
}
}
// TestNegativeKerML covers the KerML notation whose malformed variants must
// still be reported: accepting them would be a conformance failure of its own.
func TestNegativeKerML(t *testing.T) {
tests := []struct {
name string
input string
}{
// `featured by` states one target or a list of them, so the keyword pair
// and every target in the list are required (KerML.xtext:569).
{"featured_without_by", "package P { class A; feature f featured A; }"},
{"featured_by_no_target", "package P { feature f featured by ; }"},
{"featured_by_trailing_comma", "package P { class A; feature f featured by A, ; }"},
{"featured_by_no_terminator", "package P { class A; feature f featured by A }"},
{"by_without_featured", "package P { class A; feature f by A; }"},
// A SysML-only word is a name in KerML, so an operator after it still
// needs its right operand.
{"sysml_word_name_missing_operand", "package P { struct S { feature action : Real; inv { action <= } } }"},
{"sysml_word_name_unclosed_comparison", "package P { struct S { feature action : Real; inv { action < } } }"},
// In KerML the word `ordered` names the feature after `composite`, so
// a name may not follow it.
{"composite_ordered_name", "class C { composite ordered x; }"},
// `composite` alone declares no feature (KerML.xtext BasicFeaturePrefix);
// SysML.xtext has no such literal, so there `composite { }` names a usage.
{"composite_body_without_name", "class C { composite { } }"},
// A parenthesized end list holds at least two ends and closes
// (KerML.xtext:842).
{"nary_connector_unclosed", "package P { feature a; feature b; connector c (a, b; }"},
{"nary_connector_trailing_comma", "package P { feature a; feature b; connector c (a, b, ); }"},
{"nary_connector_empty", "package P { connector c (); }"},
{"nary_connector_one_end", "package P { feature a; connector c (a); }"},
{"nary_connector_end_no_target", "package P { feature a; connector c ([1], a); }"},
// A binary connector states both ends around `to` (KerML.xtext:836).
{"binary_connector_no_second_end", "package P { feature a; connector a to ; }"},
{"binary_connector_no_first_end", "package P { feature t; connector to t; }"},
{"binary_connector_no_to", "package P { feature eng; feature t; connector eng t; }"},
{"binary_connector_end_multiplicity_no_target", "package P { feature a; feature b; connector [1] to b; }"},
{"binary_connector_named_end_no_target", "package P { feature a; feature b; connector e ::> to b; }"},
{"binary_connector_name_without_from", "package P { feature a; feature b; connector c : L a to b; }"},
// A parenthesized sequence admits one trailing comma after an operand
// (KerMLExpressions.xtext SequenceExpression), never in its place.
{"sequence_two_trailing_commas", "package P { feature s = (1, 2,,); }"},
{"sequence_comma_only", "package P { feature s = (,); }"},
// A succession states both ends around `then` (KerML.xtext:891).
{"succession_declaration_no_then", "package P { behavior B { step a; step b; succession s : L [1] first a b; } }"},
{"succession_declaration_no_target", "package P { behavior B { step a; succession s : L [1] first a then ; } }"},
{"succession_declaration_no_ends", "package P { behavior B { succession s : L [1] first then; } }"},
// An end's crossing multiplicity is followed by its end (KerML.xtext:854), so
// one followed by nothing or the other end's keyword, or left unclosed, is reported.
{"binding_end_multiplicity_no_end", "package P { feature a; feature b; binding [1] = b; }"},
{"binding_end_multiplicity_unclosed", "package P { feature a; feature b; binding [1 a = b; }"},
{"binding_second_end_multiplicity_no_end", "package P { feature a; feature b; binding [1] a = [1] ; }"},
{"binding_end_multiplicity_no_terminator", "package P { feature a; feature b; binding [1] a = [1] b }"},
// A named binding end references a feature after its `::>` or `references`
// (KerML.xtext ConnectorEnd), and `of` is followed by two ends around '='.
{"binding_named_end_no_target", "package P { feature a; feature b; binding e1 ::> = b; }"},
{"binding_of_named_end_no_target", "package P { feature a; feature b; binding of e1 references = b; }"},
{"binding_of_no_ends", "package P { feature a; feature b; binding x of ; }"},
{"binding_of_no_second_end", "package P { feature a; feature b; binding of a = ; }"},
{"binding_ends_then", "package P { feature a; feature b; feature c; binding a = b then c; }"},
{"binding_named_end_expression", "package P { feature a; feature b; binding of e1 ::> a = b + 1; }"},
{"succession_end_multiplicity_no_end", "package P { behavior B { step a; step b; succession [1] then b; } }"},
{"succession_end_multiplicity_unclosed", "package P { behavior B { step a; step b; succession [1 a then b; } }"},
{"succession_second_end_multiplicity_no_end", "package P { behavior B { step a; step b; succession [1] a then [1] ; } }"},
{"succession_end_multiplicity_no_terminator", "package P { behavior B { step a; step b; succession [1] a then [1] b } }"},
{"succession_named_end_no_target", "package P { behavior B { step a; step b; succession [1] e ::> then b; } }"},
{"succession_global_end_no_name", "package P { behavior B { step a; step b; succession [1] $:: then b; } }"},
// A named multiplicity states bounds or a subsetting, and the
// subsetting names one multiplicity (KerML.xtext:754).
{"multiplicity_subsets_no_target", "package P { multiplicity m subsets ; }"},
{"multiplicity_subsets_no_terminator", "package P { multiplicity zeroOrMore [0..*]; multiplicity m subsets zeroOrMore }"},
{"multiplicity_bounds_and_subsets", "package P { multiplicity zeroOrMore [0..*]; multiplicity m [0..*] subsets zeroOrMore; }"},
// An index states a sequence of at least one expression and closes
// (KerMLExpressions.xtext PrimaryExpression); the sequence admits one
// trailing comma after an operand (SequenceExpression), never in its place.
{"index_sequence_unclosed", "package P { feature a; feature b = a#(1, 2; }"},
{"index_sequence_empty", "package P { feature a; feature b = a#(); }"},
{"index_sequence_two_trailing_commas", "package P { feature a; feature b = a#(1,,); }"},
{"index_sequence_comma_only", "package P { feature a; feature b = a#(,); }"},
{"index_bracket_unclosed", "package P { feature a; feature b = a[1, 2; }"},
{"index_bracket_comma_only", "package P { feature a; feature b = a[,]; }"},
// A word the KerML grammar does not reserve names a feature, so a
// declaration so named that is malformed is still reported.
{"feature_named_merge_no_type", "package P { feature merge : ; }"},
{"feature_named_at_no_terminator", "package P { feature at }"},
{"import_named_while_no_terminator", "package P { public import while }"},
// A KerML literal states its relationship where a name would go, so it
// does not name a feature there (the pinned KerML validator answers
// "no viable alternative at input 'chains'").
{"feature_named_chains", "package P { class T; feature chains : T; }"},
// A word KerML.xtext does not reserve names a feature, so a malformed
// declaration so named is still reported.
{"feature_named_frame_no_type", "package P { feature frame : ; }"},
{"feature_named_state_no_terminator", "package P { feature state }"},
// A terminate action usage is an action usage (SysML.xtext TerminateNode);
// `terminate` closes no other kind of usage.
{"part_terminate", "package P { part p terminate; }"},
{"attribute_terminate", "package P { part def D { attribute x : Integer terminate; } }"},
{"state_terminate", "package P { state def S { state s terminate; } }"},
// The marker closes the declaration (`ActionNodeUsageDeclaration? 'terminate'
// ActionBody`): nothing but the body follows it.
{"terminate_repeated", "package P { action def A { first start; then stop; action stop terminate terminate; } }"},
{"terminate_then_typing", "package P { action def A { first start; then stop; action stop terminate : Brake; } }"},
{"terminate_then_ordered", "package P { action def A { first start; then stop; action stop terminate ordered; } }"},
{"terminate_then_value", "package P { action def A { first start; then stop; action stop terminate = 3; } }"},
{"terminate_then_target", "package P { action def A { first start; then stop; action stop terminate stop; } }"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := parser.New(source.New(tt.name+".kerml", []byte(tt.input)))
_ = p.ParseFile()
if len(p.Diagnostics) == 0 {
t.Errorf("Expected parse errors for malformed input, got none.\nInput: %s", tt.input)
}
})
}
}
// An unterminated comment swallows the rest of the document, so the parser says
// so rather than silently returning a tree that is missing everything after it.