OILS / test / ysh-parse-errors.sh View on Github | oilshell.org

1689 lines, 580 significant
1#!/usr/bin/env bash
2#
3# Usage:
4# test/ysh-parse-errors.sh <function name>
5
6set -o nounset
7set -o pipefail
8set -o errexit
9
10source test/common.sh
11source test/sh-assert.sh # _assert-sh-status
12
13#
14# Cases
15#
16
17test-return-args() {
18 _ysh-should-parse '
19 func foo(x) {
20 return (x)
21 }
22 '
23
24 _ysh-parse-error '
25 func foo(x) {
26 return ()
27 }
28 '
29
30 _ysh-parse-error '
31 func foo(x) {
32 return (named=x)
33 }
34 '
35
36 _ysh-parse-error '
37 func foo(x) {
38 return (x, named=x)
39 }
40 '
41
42 _ysh-parse-error '
43 func foo(x) {
44 return (x, x)
45 }
46 '
47}
48
49test-func-var-checker() {
50 _ysh-should-parse '
51 func f(x) {
52 setvar x = true
53 }
54 '
55
56 _ysh-parse-error '
57 func f() {
58 setvar x = true
59 }
60 '
61}
62
63test-arglist() {
64 _ysh-parse-error 'json write ()'
65
66 # named args allowed in first group
67 _ysh-should-parse 'json write (42, indent=1)'
68 _ysh-should-parse 'json write (42; indent=2)'
69
70 _ysh-should-parse '= toJson(42, indent=1)'
71 _ysh-should-parse '= toJson(42; indent=2)'
72
73 # Named group only
74 _ysh-should-parse 'p (; n=true)'
75 _ysh-should-parse '= f(; n=true)'
76
77 # Empty named group
78 _ysh-should-parse 'p (;)'
79 _ysh-should-parse '= f(;)'
80
81 _ysh-should-parse 'p (42;)'
82 _ysh-should-parse '= f(42;)'
83
84 # No block group in func arg lists
85 _ysh-parse-error '= f(42; n=true; block)'
86 _ysh-parse-error '= f(42; ; block)'
87
88 # Block expressions in proc arg lists
89 _ysh-should-parse 'p (42; n=true; block)'
90 _ysh-should-parse 'p (42; ; block)'
91
92 _ysh-parse-error 'p (42; n=42; bad=3)'
93 _ysh-parse-error 'p (42; n=42; ...bad)'
94
95 # Positional args can't appear in the named section
96 _ysh-parse-error '= f(; 42)'
97 _ysh-parse-error '= f(; name)'
98 _ysh-parse-error '= f(; x for x in y)'
99}
100
101
102# Extra constraints on param groups:
103# - word arg types can only be Str or Ref
104# - no constraints on positional or keyword args?
105# - they have optional types, and optional default vals
106# - block param:
107# - there can only be one
108# - no rest param either
109# - default value is null only?
110
111test-proc-sig() {
112 _ysh-should-parse 'proc p () { echo hi }'
113 _ysh-should-parse 'proc p (a) { echo hi }'
114 _ysh-should-parse 'proc p (out Ref) { echo hi }'
115
116 # doesn't make sense I think -- they're all strings. Types don't do any
117 # dynamic validation, except 'out Ref' does change semantics
118 _ysh-parse-error 'proc p (a Int) { echo hi }'
119
120 _ysh-parse-error 'proc p (w, ...) { echo hi }'
121
122 _ysh-should-parse 'proc p (w, ...rest) { echo hi }'
123
124 # Hm I guess this is fine
125 _ysh-should-parse 'proc p (; n Int=3) { echo hi }'
126
127 _ysh-should-parse 'proc p (out Ref; n Int=3) { echo hi }'
128
129 _ysh-should-parse 'proc p (; ; n Int=3) { echo hi }'
130
131 _ysh-should-parse 'proc p ( ; ; ; block) { echo hi }'
132
133 _ysh-should-parse 'proc p (w, ...rest) { echo hi }'
134 _ysh-should-parse 'proc p (w, ...rest; t) { echo hi }'
135
136 _ysh-should-parse 'func p (p, ...rest) { echo hi }'
137
138 _ysh-should-parse 'func p (p, ...rest; n, ...named) { echo hi }'
139 _ysh-should-parse 'func p (p, ...rest; n, ...named,) { echo hi }'
140
141 _ysh-parse-error 'func p (p, ...rest; n, ...named, z) { echo hi }'
142 _ysh-parse-error 'func p (p, ...rest; n, ...named; ) { echo hi }'
143
144 _ysh-should-parse 'proc p (w, ...rest; pos, ...named) { echo hi }'
145
146 _ysh-should-parse 'proc p (w, ...rest; pos, ...args; named=3, ...named) { echo hi }'
147
148 _ysh-should-parse 'proc p (w=1, v=2; p=3, q=4; n=5, m=6) { echo hi }'
149
150 _ysh-parse-error 'proc p (w Int Int) { echo hi }'
151
152 _ysh-should-parse 'proc p (w=1, v=2; p Int=3, q List[Int] = [3, 4]; n Int=5, m Int = 6) { echo hi }'
153
154 _ysh-should-parse 'proc p (w, ...rest; t, ...args; n, ...named; block) { echo hi }'
155
156 _ysh-parse-error 'proc p ( ; ; ; b1, b2) { echo hi }'
157 _ysh-parse-error 'proc p ( ; ; ; b1, ...rest) { echo hi }'
158 _ysh-parse-error 'proc p ( ; ; ; b1 Str) { echo hi }'
159
160 # Only Command type
161 _ysh-should-parse 'proc p ( ; ; ; b Command) { echo hi }'
162
163 # bad param
164 _ysh-parse-error 'proc p ( ; ; ; b Command[Int]) { echo hi }'
165
166 _ysh-should-parse 'proc p ( ; ; ; ) { echo hi }'
167}
168
169test-proc-def() {
170 _ysh-parse-error 'proc p(w) { var w = foo }'
171 _ysh-parse-error 'proc p(w; p) { var p = foo }'
172 _ysh-parse-error 'proc p(w; p; n, n2) { var n2 = foo }'
173 _ysh-parse-error 'proc p(w; p; n, n2; b) { var b = foo }'
174}
175
176test-typed-proc() {
177 _ysh-should-parse 'typed proc p(words) { echo hi }'
178 _ysh-parse-error 'typed zzz p(words) { echo hi }'
179 _ysh-parse-error 'typed p(words) { echo hi }'
180}
181
182test-func-sig() {
183 _ysh-parse-error 'func f { echo hi }'
184
185 _ysh-should-parse 'func f () { echo hi }'
186
187 _ysh-should-parse 'func f (a List[Int] = [3,4]) { echo hi }'
188 _ysh-should-parse 'func f (a, b, ...rest; c) { echo hi }'
189 _ysh-should-parse 'func f (a, b, ...rest; c, ...named) { echo hi }'
190 _ysh-parse-error 'func f (a, b, ...rest; c, ...named;) { echo hi }'
191}
192
193test-func-def() {
194 _ysh-parse-error 'func f(p) { var p = foo }'
195 _ysh-parse-error 'func f(p; n) { var n = foo }'
196}
197
198test-sh-assign() {
199 _ysh-should-parse 'x=y'
200 _ysh-should-parse 'x=y echo hi'
201 _ysh-should-parse 'f() { x=y; }'
202
203 # Disallowed in YSH
204 _ysh-parse-error 'func f() { x=y; }'
205 _ysh-parse-error 'proc p { x=y; }'
206
207 # Only proc and func disallow it
208 _ysh-should-parse '{ x=y; }'
209 _ysh-should-parse '( x=y; )'
210
211 _assert-sh-status 0 $YSH 'Expected it to parse' \
212 -o ysh:upgrade -n -c 'x=y'
213}
214
215test-ysh-var() {
216 # Unterminated
217 _ysh-parse-error 'var x = 1 + '
218
219 _ysh-parse-error 'var x = * '
220
221 _ysh-parse-error 'var x = @($(cat <<EOF
222here doc
223EOF
224))'
225
226 # Hm we need a ; after var or setvar
227 _ysh-should-parse 'var x = $(var x = 1; )'
228 _ysh-should-parse '
229 var x = $(var x = 1
230)'
231 # This doesn't have it
232 _ysh-parse-error 'var x = $(var x = 1)'
233
234 # Extra )
235 _ysh-parse-error 'var x = $(var x = 1; ))'
236 _ysh-parse-error 'var x = $(var x = 1; ) )'
237}
238
239test-ysh-expr() {
240 # old syntax
241 _ysh-parse-error '= 5 mod 3'
242
243 _ysh-parse-error '= >>='
244 _ysh-parse-error '= %('
245
246 # Singleton tuples
247 _ysh-parse-error '= 42,'
248 _ysh-parse-error '= (42,)'
249
250 # Disallowed unconditionally
251 _ysh-parse-error '=a'
252
253 _ysh-parse-error '
254 var d = {}
255 = d["foo", "bar"]
256 '
257}
258
259test-ysh-expr-more() {
260 # user must choose === or ~==
261 _ysh-parse-error 'if (5 == 5) { echo yes }'
262
263 _ysh-should-parse 'echo $[join(x)]'
264
265 _ysh-parse-error 'echo $join(x)'
266
267 _ysh-should-parse 'echo @[split(x)]'
268 _ysh-should-parse 'echo @[split(x)] two'
269
270 _ysh-parse-error 'echo @[split(x)]extra'
271
272 # Old syntax that's now invalid
273 _ysh-parse-error 'echo @split("a")'
274}
275
276test-blocks() {
277 _ysh-parse-error '>out { echo hi }'
278 _ysh-parse-error 'a=1 b=2 { echo hi }'
279 _ysh-parse-error 'break { echo hi }'
280 # missing semicolon
281 _ysh-parse-error 'cd / { echo hi } cd /'
282}
283
284test-parse-brace() {
285 # missing space
286 _ysh-parse-error 'if test -f foo{ echo hi }'
287}
288
289test-proc-sig() {
290 _ysh-parse-error 'proc f[] { echo hi }'
291 _ysh-parse-error 'proc : { echo hi }'
292 _ysh-parse-error 'proc foo::bar { echo hi }'
293}
294
295test-regex-literals() {
296 _ysh-parse-error 'var x = / ! /'
297 _ysh-should-parse 'var x = / ![a-z] /'
298
299 _ysh-should-parse 'var x = / !d /'
300
301 _ysh-parse-error 'var x = / !! /'
302
303 # missing space between rangfes
304 _ysh-parse-error 'var x = /[a-zA-Z]/'
305 _ysh-parse-error 'var x = /[a-z0-9]/'
306
307 _ysh-parse-error 'var x = /[a-zz]/'
308
309 # can't have multichar ranges
310 _ysh-parse-error "var x = /['ab'-'z']/"
311
312 # range endpoints must be constants
313 _ysh-parse-error 'var x = /[$a-${z}]/'
314
315 # These are too long too
316 _ysh-parse-error 'var x = /[abc]/'
317
318 # Single chars not allowed, should be /['%_']/
319 _ysh-parse-error 'var x = /[% _]/'
320
321}
322
323test-hay-assign() {
324 _ysh-parse-error '
325name = val
326'
327
328 _ysh-parse-error '
329rule {
330 x = 42
331}
332'
333
334 _ysh-parse-error '
335RULE {
336 x = 42
337}
338'
339
340 _ysh-should-parse '
341Rule {
342 x = 42
343}
344'
345
346 _ysh-should-parse '
347Rule X Y {
348 x = 42
349}
350'
351
352 _ysh-should-parse '
353RULe { # inconsistent but OK
354 x = 42
355}
356'
357
358 _ysh-parse-error '
359hay eval :result {
360
361 Rule {
362 foo = 42
363 }
364
365 bar = 43 # parse error here
366}
367'
368
369 _ysh-parse-error '
370hay define TASK
371
372TASK build {
373 foo = 42
374}
375'
376
377 # CODE node nested inside Attr node.
378 _ysh-parse-error '
379hay define Package/TASK
380
381Package libc {
382 TASK build {
383 # this is not an attribute, should not be valid
384 foo = 42
385 }
386}
387'
388
389 _ysh-parse-error '
390hay define Rule
391
392Rule {
393 return (x)
394}
395'
396
397 return
398 # This is currently allowed, arguably shouldn't be
399
400 _ysh-parse-error '
401hay define Rule
402
403Rule {
404 return 42
405}
406'
407}
408
409test-hay-shell-assign() {
410 _ysh-parse-error '
411hay define Package
412
413Package foo {
414 version=1
415}
416'
417
418 _ysh-parse-error '
419hay define Package/User
420
421Package foo {
422 User bob {
423 sudo=1
424 }
425}
426'
427
428 _ysh-should-parse '
429hay define Package/SHELL/User
430
431Package foo {
432 SHELL bob {
433 sudo=1
434 User {
435 name = "z"
436 }
437 }
438}
439'
440
441 _ysh-parse-error '
442hay define Package/SHELL/User
443
444Package foo {
445 SHELL bob {
446 # Disallowed
447 # a = b
448 User {
449 x=1
450 }
451 }
452}
453'
454
455 return
456
457 # It's OK that this parses, we didn't use the CapsWord style
458
459 _ysh-parse-error '
460hay define package user TASK
461
462hay eval :result {
463 package foo {
464 version=1
465 }
466}
467'
468}
469
470test-parse-at() {
471 _ysh-parse-error 'echo @'
472 _ysh-parse-error 'echo @@'
473 _ysh-parse-error 'echo @{foo}'
474 _ysh-parse-error 'echo @/foo/'
475 _ysh-parse-error 'echo @"foo"'
476}
477
478test-ysh-nested-proc-func() {
479 _ysh-parse-error 'proc p { echo 1; proc f { echo f }; echo 2 }'
480 _ysh-parse-error 'func f() { echo 1; proc f { echo f }; echo 2 }'
481 _ysh-parse-error 'proc p { echo 1; func f() { echo f }; echo 2 }'
482 _ysh-parse-error 'func f() { echo 1; func f2() { echo f }; echo 2 }'
483
484 _ysh-parse-error 'proc p { echo 1; +weird() { echo f; }; echo 2 }'
485
486 # ksh function
487 _ysh-parse-error 'proc p { echo 1; function f { echo f; }; echo 2 }'
488
489 _ysh-parse-error 'f() { echo 1; proc inner { echo inner; }; echo 2; }'
490
491 # shell nesting is still allowed
492 _ysh-should-parse 'f() { echo 1; g() { echo g; }; echo 2; }'
493
494 _ysh-should-parse 'proc p() { shopt --unset errexit { false hi } }'
495}
496
497test-int-literals() {
498 _ysh-should-parse '= 42'
499 _ysh-should-parse '= 42_0'
500 _ysh-parse-error '= 42_'
501 _ysh-parse-error '= 42_0_'
502
503 # this is a var name
504 _ysh-should-parse '= _42'
505}
506
507test-float-literals() {
508 _ysh-should-parse '= 42.0'
509 _ysh-should-parse '= 42_0.0'
510 _ysh-parse-error '= 42_.0'
511
512 _ysh-parse-error '= 42.'
513 _ysh-parse-error '= .333'
514
515 _ysh-parse-error '= _42.0'
516}
517
518test-lhs-expr() {
519 _ysh-should-parse 'setvar x.y[z] = 42'
520 _ysh-should-parse 'setvar a[i][j] = 42'
521
522 _ysh-should-parse 'setvar a[i], d.key = 42, 43'
523 _ysh-parse-error 'setvar a[i], 3 = 42, 43'
524 _ysh-parse-error 'setvar a[i], {}["key"] = 42, 43'
525
526 _ysh-parse-error 'setvar x+y = 42'
527
528 # method call
529 _ysh-parse-error 'setvar x->y = 42'
530
531 # this is allowed
532 _ysh-should-parse 'setglobal a[{k:3}["k"]] = 42'
533
534 _ysh-parse-error 'setglobal {}["k"] = 42'
535 _ysh-parse-error 'setglobal [1,2][0] = 42'
536}
537
538test-destructure() {
539 _ysh-parse-error '
540 func f() {
541 const x, y = 3, 4
542
543 #setvar x = 5
544
545 setvar y = 6
546 }'
547
548 _ysh-parse-error '
549 func f() {
550 var x, y = 3, 4
551
552 var y = 6
553 }'
554
555 _ysh-parse-error '
556 func f() {
557 var x, y = 3, 4
558
559 const y = 6
560 }'
561}
562
563test-lazy-arg-list() {
564 _ysh-should-parse 'assert [42 === x]'
565
566 _ysh-should-parse 'assert [ 42 === x ]'
567 _ysh-should-parse 'assert [42, 43]'
568 _ysh-should-parse 'assert [42, named=true]'
569 _ysh-should-parse 'assert [42, named=true]; echo hi'
570
571 _ysh-should-parse 'assert [42, named=true] { echo hi }'
572
573 # Seems fine
574 _ysh-should-parse 'assert [42, named=true]{ echo hi }'
575
576 # I guess this legacy is still valid? Or disallow explicitly
577 _ysh-should-parse 'assert *.[ch]'
578 _ysh-should-parse 'assert 42[ch]'
579 _ysh-should-parse 'echo[]'
580
581 _ysh-parse-error 'assert [4'
582 _ysh-parse-error 'assert [ 4'
583
584 _ysh-should-parse 'json write (42) >out'
585
586 # I guess this is OK
587 _ysh-should-parse 'json write >out (42)'
588
589 # BUG
590 #_ysh-parse-error 'when (42) >out { echo hi }'
591
592 #_ysh-should-parse 'when (42) { echo hi } >out'
593
594 # How to support this? Maybe the CommandParser can test for i == 0 when it
595 # gets Op_LBracket
596
597 # legacy
598 _ysh-should-parse '[ x = y ]'
599}
600
601test-place-expr() {
602 _ysh-should-parse 'read (&x)'
603
604 # TODO: parse these into something
605 _ysh-parse-error 'read (&x[0])'
606 _ysh-parse-error 'read (&x[0][1])'
607
608 _ysh-parse-error 'read (&x.key.other)'
609
610 # This is a runtime error, not a parse time error
611 _ysh-should-parse 'read (&x + 1)'
612
613 _ysh-parse-error 'read (&42)'
614 _ysh-parse-error 'read (&+)'
615
616 # Place expressions aren't parenthesized expressions
617 _ysh-parse-error 'read (&(x))'
618}
619
620test-units-suffix() {
621 _ysh-parse-error '= 100 M M'
622
623 _ysh-parse-error '= 100 M; echo'
624 _ysh-parse-error '= 100 Mi; echo'
625
626 _ysh-parse-error '= 9.9 Mi; echo'
627
628 # This is confusing, could disallow, or just rely on users not to type it
629 _ysh-parse-error '= 9.9e-1 Mi; echo'
630
631 # I don't like this, but it follows lexing rules I guess
632 _ysh-parse-error '= 100Mi'
633
634 _ysh-parse-error '= [100 Mi, 200 Mi]'
635
636 _ysh-parse-error '= {[42 Ki]: 43 Ki}'
637}
638
639test-type-expr() {
640 # This is nicer
641 _ysh-should-parse 'var x: Int = f()'
642
643 # But colon is optional
644 _ysh-should-parse 'var x Int = f()'
645
646 # Colon is noisy here because we have semi-colons
647 _ysh-should-parse 'proc p (; x Int, y Int; ) { echo hi }'
648
649 _ysh-should-parse 'func f (x Int, y Int; z Int = 0) { echo hi }'
650
651 # Hm should these be allowed, but discouraged?
652 #_ysh-should-parse 'func f (x Int, y Int; z: Int = 0) { echo hi }'
653 #_ysh-should-parse 'proc p (; x: Int, y: Int;) { echo hi }'
654}
655
656test-no-const() {
657 _ysh-should-parse 'const x = 42'
658
659 # Must be at the top level
660 _ysh-parse-error '
661 proc p {
662 const x = 42
663 }'
664
665 _ysh-parse-error '
666 func f() {
667 const x = 42
668 }'
669}
670
671test-fat-arrow() {
672 _ysh-should-parse 'var x = s => trim()'
673 _ysh-should-parse 'func f(x Int) => List[Int] { echo hi }'
674}
675
676# Backslash in UNQUOTED context
677test-parse-backslash() {
678 _ysh-should-parse 'echo \('
679 _ysh-should-parse 'echo \;'
680 _ysh-should-parse 'echo ~'
681 _ysh-should-parse 'echo \!' # history?
682
683 _ysh-should-parse 'echo \%' # job ID? I feel like '%' is better
684 _ysh-should-parse 'echo \#' # comment
685
686 _ysh-parse-error 'echo \.'
687 _ysh-parse-error 'echo \-'
688 _ysh-parse-error 'echo \/'
689
690 _ysh-parse-error 'echo \a'
691 _ysh-parse-error 'echo \Z'
692 _ysh-parse-error 'echo \0'
693 _ysh-parse-error 'echo \9'
694
695 _osh-should-parse 'echo \. \- \/ \a \Z \0 \9'
696}
697
698test-make-these-nicer() {
699 # expects expression on right
700 _ysh-parse-error '='
701 _ysh-parse-error 'call'
702
703 # What about \u{123} parse errors
704 # I get a warning now, but parse_backslash should give a syntax error
705 # _ysh-parse-error "x = c'\\uz'"
706
707 # Dict pair split
708 _ysh-parse-error 'const d = { name:
70942 }'
710
711 #_ysh-parse-error ' d = %{}'
712}
713
714test-var-decl() {
715 _ysh-parse-error '
716 proc p(x) {
717 echo hi
718 var x = 2 # Cannot redeclare param
719 }
720 '
721
722 _ysh-parse-error '
723 proc p {
724 var x = 1
725 echo hi
726 var x = 2 # Cannot redeclare local
727 }
728 '
729
730 _ysh-parse-error '
731 proc p(x, :out) {
732 var out = 2 # Cannot redeclare out param
733 }
734 '
735
736 _ysh-parse-error '
737 proc p {
738 var out = 2 # Cannot redeclare out param
739 cd /tmp {
740 var out = 3
741 }
742 }
743 '
744
745 _ysh-should-parse '
746 var x = 1
747 proc p {
748 echo hi
749 var x = 2
750 }
751
752 proc p2 {
753 var x = 3
754 }
755 '
756}
757
758test-setvar() {
759 _ysh-should-parse '
760 proc p(x) {
761 var y = 1
762 setvar y = 42
763 }
764 '
765
766 _ysh-parse-error '
767 proc p(x) {
768 var y = 1
769 setvar L = "L" # ERROR: not declared
770 }
771 '
772
773 _ysh-parse-error '
774 proc p(x) {
775 var y = 1
776 setvar L[0] = "L" # ERROR: not declared
777 }
778 '
779
780 _ysh-parse-error '
781 proc p(x) {
782 var y = 1
783 setvar d.key = "v" # ERROR: not declared
784 }
785 '
786
787 _ysh-should-parse '
788 proc p(x) {
789 setvar x = "X" # is mutating params allowed? I guess why not.
790 }
791 '
792}
793
794test-ysh-case() {
795 _ysh-should-parse '
796 case (x) {
797 (else) { = 1; }
798 }
799 '
800
801 _ysh-should-parse '
802 var myexpr = ^[123]
803
804 case (123) {
805 (myexpr) { echo 1 }
806 }
807 '
808
809 _ysh-should-parse '
810 case (x) {
811 (else) { echo 1 }
812 }
813 '
814
815 _ysh-should-parse '
816 case (x) {
817 (else) { = 1 }
818 }
819 '
820
821 _ysh-should-parse '
822 case (x) {
823 (else) { = 1 }
824
825 }
826 '
827
828 _ysh-should-parse '
829 case (x) {
830 (else) { = 1 } # Comment
831 }
832 '
833
834 _ysh-should-parse '
835 case (3) {
836 (3) { echo hi }
837 # comment line
838 }
839 '
840
841 _ysh-should-parse '
842 case (x) {
843 (else) { echo 1 }
844 }
845 '
846
847 _ysh-should-parse '
848 case (foo) { (else) { echo } }
849 '
850
851 _ysh-should-parse '
852 case (foo) {
853 *.py { echo "python" }
854 }
855 '
856
857 _ysh-should-parse '
858 case (foo) {
859 (obj.attr) { echo "python" }
860 }
861 '
862
863 _ysh-should-parse '
864 case (foo) {
865 (0) { echo "python" }
866 }
867 '
868
869 _ysh-should-parse '
870 case (foo) {
871 ("main.py") { echo "python" }
872 }
873 '
874
875 # Various multi-line cases
876 if false; then # TODO: fixme, this is in the vein of the `if(x)` error
877 _ysh-should-parse '
878 case (foo){("main.py"){ echo "python" } }
879 '
880 fi
881 _ysh-should-parse '
882 case (foo) { ("main.py") { echo "python" } }
883 '
884 _ysh-should-parse '
885 case (foo) {
886 ("main.py") {
887 echo "python" } }'
888 _ysh-should-parse '
889 case (foo) {
890 ("main.py") {
891 echo "python" }
892 }
893 '
894 _ysh-should-parse '
895 case (foo) {
896 ("main.py") { echo "python"
897 }
898 }
899 '
900 _ysh-should-parse '
901 case (foo) {
902 ("main.py") {
903 echo "python"
904 }
905 }
906 '
907
908 # Example valid cases from grammar brain-storming
909 _ysh-should-parse '
910 case (add(10, 32)) {
911 (40 + 2) { echo Found the answer }
912 (else) { echo Incorrect
913 }
914 }
915 '
916
917 _ysh-should-parse "
918 case (file) {
919 / dot* '.py' / {
920 echo Python
921 }
922
923 / dot* ('.cc' | '.h') /
924 {
925 echo C++
926 }
927 }
928 "
929 _ysh-should-parse '
930 case (lang) {
931 en-US
932 | en-CA
933 | en-UK {
934 echo Hello
935 }
936 fr-FR |
937 fr-CA {
938 echo Bonjour
939 }
940
941
942
943
944
945 (else) {
946 echo o/
947 }
948 }
949 '
950
951 _ysh-should-parse '
952 case (num) {
953 (1) | (2) {
954 echo number
955 }
956 }
957 '
958
959 _ysh-should-parse '
960 case (num) {
961 (1) | (2) | (3)
962 | (4) | (5) {
963 echo small
964 }
965
966 (else) {
967 echo large
968 }
969 }
970 '
971
972 # Example invalid cases from grammar brain-storming
973 _ysh-parse-error '
974 case
975 (add(10, 32)) {
976 (40 + 2) { echo Found the answer }
977 (else) { echo Incorrect }
978 }
979 '
980 _ysh-parse-error "
981 case (file)
982 {
983 ('README') | / dot* '.md' / { echo Markdown }
984 }
985 "
986 _ysh-parse-error '
987 case (file)
988 {
989 {
990 echo Python
991 }
992 }
993 '
994 _ysh-parse-error '
995 case (file)
996 {
997 cc h {
998 echo C++
999 }
1000 }
1001 '
1002 _ysh-parse-error "
1003 case (lang) {
1004 en-US
1005 | ('en-CA')
1006 | / 'en-UK' / {
1007 echo Hello
1008 }
1009 }
1010 "
1011 _ysh-parse-error '
1012 case (lang) {
1013 else) {
1014 echo o/
1015 }
1016 }
1017 '
1018 _ysh-parse-error '
1019 case (num) {
1020 (1) | (2) | (3)
1021 | (4) | (5) {
1022 echo small
1023 }
1024
1025 (6) | (else) {
1026 echo large
1027 }
1028 }
1029 '
1030
1031 _ysh-parse-error '
1032 case $foo {
1033 ("main.py") {
1034 echo "python"
1035 }
1036 }
1037 '
1038
1039 # Newline not allowed, because it isn't in for, if, while, etc.
1040 _ysh-parse-error '
1041 case (x)
1042 {
1043 *.py { echo "python" }
1044 }
1045 '
1046
1047 _ysh-parse-error '
1048 case (foo) in
1049 *.py {
1050 echo "python"
1051 }
1052 esac
1053 '
1054
1055 _ysh-parse-error '
1056 case $foo {
1057 bar) {
1058 echo "python"
1059 }
1060 }
1061 '
1062
1063 _ysh-parse-error '
1064 case (x) {
1065 {
1066 echo "python"
1067 }
1068 }
1069 '
1070
1071 _ysh-parse-error '
1072 case (x {
1073 *.py { echo "python" }
1074 }
1075 '
1076
1077 _ysh-parse-error '
1078 case (x) {
1079 *.py) { echo "python" }
1080 }
1081 '
1082
1083 _ysh-should-parse "case (x) { word { echo word; } (3) { echo expr; } /'eggex'/ { echo eggex; } }"
1084
1085 _ysh-should-parse "
1086case (x) {
1087 word { echo word; } (3) { echo expr; } /'eggex'/ { echo eggex; } }"
1088
1089 _ysh-should-parse "
1090case (x) {
1091 word { echo word; }
1092 (3) { echo expr; } /'eggex'/ { echo eggex; } }"
1093
1094 _ysh-should-parse "
1095case (x) {
1096 word { echo word; }
1097 (3) { echo expr; }
1098 /'eggex'/ { echo eggex; } }"
1099
1100 _ysh-should-parse "
1101case (x) {
1102 word { echo word; }
1103 (3) { echo expr; }
1104 /'eggex'/ { echo eggex; }
1105}"
1106
1107 # No leading space
1108 _ysh-should-parse "
1109case (x) {
1110word { echo word; }
1111(3) { echo expr; }
1112/'eggex'/ { echo eggex; }
1113}"
1114}
1115
1116test-ysh-for() {
1117 _ysh-should-parse '
1118 for x in (obj) {
1119 echo $x
1120 }
1121 '
1122
1123 _ysh-parse-error '
1124 for x in (obj); do
1125 echo $x
1126 done
1127 '
1128
1129 _ysh-should-parse '
1130 for x, y in SPAM EGGS; do
1131 echo $x
1132 done
1133 '
1134
1135 # Bad loop variable name
1136 _ysh-parse-error '
1137 for x-y in SPAM EGGS; do
1138 echo $x
1139 done
1140 '
1141
1142 # Too many indices
1143 _ysh-parse-error '
1144 for x, y, z in SPAM EGGS; do
1145 echo $x
1146 done
1147 '
1148
1149 _ysh-parse-error '
1150 for w, x, y, z in SPAM EGGS; do
1151 echo $x
1152 done
1153 '
1154
1155 # Old style
1156 _ysh-should-parse '
1157 for x, y in SPAM EGGS
1158 do
1159 echo $x
1160 done
1161 '
1162
1163 # for shell compatibility, allow this
1164 _ysh-should-parse 'for const in (x) { echo $var }'
1165}
1166
1167test-for-parse-bare-word() {
1168 _ysh-parse-error '
1169 for x in bare {
1170 echo $x
1171 }
1172 '
1173
1174 _ysh-should-parse '
1175 for x in a b {
1176 echo $x
1177 }
1178 '
1179
1180 _ysh-should-parse '
1181 for x in *.py {
1182 echo $x
1183 }
1184 '
1185
1186 _ysh-should-parse '
1187 for x in "quoted" {
1188 echo $x
1189 }
1190 '
1191}
1192
1193test-for() {
1194 # Technically we could switch to a different lexer mode here, but it seems
1195 # easy enough to reuse the Id.Redir_LessGreat token
1196 _ysh-parse-error '
1197 for x in <> {
1198 echo $x
1199 }
1200 '
1201
1202 _ysh-parse-error '
1203 for x in <>
1204 {
1205 echo $x
1206 }
1207 '
1208
1209 _ysh-parse-error '
1210 for x in < {
1211 echo $x
1212 }
1213 '
1214
1215 # Space between < >
1216 _ysh-parse-error '
1217 for x in < > {
1218 echo $x
1219 }
1220 '
1221}
1222
1223test-bug-1118() {
1224 # Originally pointed at 'for'
1225 _ysh-parse-error '
1226 var snippets = [{status: 42}]
1227 for snippet in (snippets) {
1228 if (snippet["status"] === 0) {
1229 echo hi
1230 }
1231
1232 # The $ causes a weird error
1233 if ($snippet["status"] === 0) {
1234 echo hi
1235 }
1236 }
1237 '
1238
1239 # Issue #1118
1240 # pointed at 'var' in count
1241 _ysh-parse-error '
1242 var content = [ 1, 2, 4 ]
1243 var count = 0
1244
1245 # The $ causes a weird error
1246 while (count < $len(content)) {
1247 setvar count += 1
1248 }
1249 '
1250}
1251
1252test-bug-1850() {
1253 _ysh-should-parse 'pp line (42); pp line (43)'
1254 #_osh-should-parse 'pp line (42); pp line (43)'
1255
1256 # Extra word is bad
1257 _ysh-parse-error 'pp line (42) extra'
1258
1259 # Bug -- newline or block should come after arg list
1260 _ysh-parse-error 'pp line (42), echo'
1261
1262 # This properly checks a similar error. It's in a word.
1263 _ysh-parse-error 'pp line @(echo), echo'
1264
1265 # Common cases
1266 _ysh-should-parse 'pp line (42)'
1267 _ysh-should-parse 'pp line (42) '
1268 _ysh-should-parse 'pp line (42);'
1269 _ysh-should-parse 'pp line (42) { echo hi }'
1270
1271 # Original bug
1272
1273 # Accidental comma instead of ;
1274 # Wow this is parsed horribly - (42) replaced (43)
1275 _ysh-parse-error 'pp line (42), pp line (43)'
1276}
1277
1278test-bug-1850-more() {
1279 ### Regression
1280
1281 _ysh-parse-error 'assert (42)extra'
1282 _ysh-parse-error 'assert (42) extra'
1283
1284 _ysh-parse-error 'assert [42]extra'
1285 _ysh-parse-error 'assert [42] extra'
1286}
1287
1288test-command-simple-more() {
1289 _ysh-should-parse 'foo=1'
1290
1291 _ysh-parse-error 'foo=1 >out (42)'
1292
1293 _ysh-parse-error 'foo=1 (42)'
1294
1295 _ysh-should-parse 'foo=1 cmd (42)'
1296
1297 _ysh-should-parse 'foo=1 cmd >out (42)'
1298}
1299
1300test-proc-args() {
1301 _osh-should-parse 'json write (x)'
1302
1303 _osh-should-parse 'echo $(json write (x))' # relies on lexer.PushHint()
1304
1305 # nested expr -> command -> expr
1306 _osh-should-parse 'var result = $(json write (x))'
1307
1308 _osh-should-parse 'json write (x, y); echo hi'
1309
1310 # named arg
1311 _osh-should-parse '
1312json write (x, name = "value")
1313echo hi
1314'
1315
1316 # with block on same line
1317 _ysh-should-parse 'json write (x) { echo hi }'
1318
1319 # with block
1320 _ysh-should-parse '
1321json write (x) {
1322 echo hi
1323}'
1324
1325 # multiple lines
1326 _osh-should-parse 'json write (
1327 x,
1328 y,
1329 z
1330 )'
1331
1332 # can't be empty
1333 _ysh-parse-error 'json write ()'
1334 _ysh-parse-error 'json write ( )'
1335
1336 # should have a space
1337 _ysh-parse-error 'json write(x)'
1338 _ysh-parse-error 'json write()'
1339 _ysh-parse-error 'f(x)' # test short name
1340}
1341
1342test-eggex-capture() {
1343 _ysh-should-parse '= / d+ /'
1344 #_ysh-should-parse '= / <d+ : date> /'
1345 _ysh-should-parse '= / < capture d+ as date > /'
1346 _ysh-should-parse '= / < capture d+ as date: Int > /'
1347
1348 # These keywords are taken in regular expressions, I guess that's OK.
1349 _ysh-parse-error 'var capture = 42'
1350 _ysh-parse-error 'var as = 42'
1351}
1352
1353
1354test-eggex-flags() {
1355 _ysh-should-parse '= / d+ ; reg_icase /'
1356 _ysh-should-parse '= / d+ ; i /' # shortcut
1357
1358 # can't negate these
1359 _ysh-parse-error '= / d+ ; !i /'
1360
1361 # typo should be parse error
1362 _ysh-parse-error '= / d+ ; reg_oops /'
1363
1364 # PCRE should not validate
1365 _ysh-should-parse '= / d+ ; !i; PCRE /'
1366 _ysh-should-parse '= / d+ ; reg_oops; PCRE /'
1367
1368 # ERE means is the default; it's POSIX ERE
1369 # Other option is PCRE
1370 _ysh-should-parse '= / d+ ; i reg_newline ; ERE /'
1371 _ysh-should-parse '= / d+ ; ; ERE /'
1372
1373 # trailing ; is OK
1374 _ysh-should-parse '= / d+ ; /'
1375
1376 # doesn't make sense
1377 _ysh-parse-error '= / d+ ; ; /'
1378 _ysh-parse-error '= / d+ ; ; ; /'
1379}
1380
1381test-string-literals() {
1382 _ysh-should-parse "echo r'hi';"
1383 #_ysh-parse-error "echo r'hi'bad"
1384
1385 _ysh-should-parse "echo u'hi'"
1386 _ysh-should-parse "(echo u'hi')"
1387
1388 _ysh-parse-error "echo b'hi'trailing"
1389 _ysh-parse-error "echo b'hi'#notcomment"
1390
1391 # This is valid shell, but not a comment
1392 _ysh-should-parse "echo 'hi'#notcomment"
1393
1394}
1395
1396test-multiline-string() {
1397 _ysh-should-parse "echo u'''
1398hi
1399'''
1400"
1401 _ysh-should-parse "echo b'''
1402hi
1403'''
1404"
1405
1406 _ysh-parse-error "echo b'''
1407hi
1408''
1409"
1410
1411 _ysh-parse-error "echo r'''
1412hi
1413'''bad
1414"
1415
1416 _ysh-parse-error "echo u'''
1417hi
1418'''bad
1419"
1420
1421 _ysh-parse-error 'echo """
1422hi
1423"""bad
1424'
1425}
1426
1427test-bug-1826() {
1428 #return
1429
1430 read -r code_str << 'EOF'
1431echo b'\xff'
1432EOF
1433
1434 _ysh-parse-error "$code_str"
1435
1436 read -r code_str << 'EOF'
1437var s = b'\xff'
1438EOF
1439
1440 _ysh-parse-error "$code_str"
1441
1442 # Backslash ending the file
1443
1444 read -r code_str << 'EOF'
1445echo b'\
1446EOF
1447 echo "[$code_str]"
1448
1449 _ysh-parse-error "$code_str"
1450
1451 read -r code_str << 'EOF'
1452var s = b'\
1453EOF
1454 echo "[$code_str]"
1455
1456 _ysh-parse-error "$code_str"
1457
1458 read -r code_str << 'EOF'
1459var s = $'\
1460EOF
1461 echo "[$code_str]"
1462
1463 _ysh-parse-error "$code_str"
1464}
1465
1466test-ysh_c_strings() {
1467 # bash syntax
1468 _osh-should-parse-here <<'EOF'
1469echo $'\u03bc'
1470EOF
1471
1472 # Extension not allowed
1473 _ysh-parse-error-here <<'EOF'
1474echo $'\u{03bc}'
1475EOF
1476
1477 # Bad syntax
1478 _ysh-parse-error-here <<'EOF'
1479echo $'\u{03bc'
1480EOF
1481
1482 # Expression mode
1483 _ysh-parse-error-here <<'EOF'
1484const bad = $'\u{03bc'
1485EOF
1486
1487 # Test single quoted
1488 _osh-should-parse-here <<'EOF'
1489echo $'\z'
1490EOF
1491 _ysh-parse-error-here <<'EOF'
1492echo $'\z'
1493EOF
1494 # Expression mode
1495 _ysh-parse-error-here <<'EOF'
1496const bad = $'\z'
1497EOF
1498
1499 # Octal not allowed
1500 _osh-should-parse-here <<'EOF'
1501echo $'\101'
1502EOF
1503 _ysh-parse-error-here <<'EOF'
1504const bad = $'\101'
1505EOF
1506
1507 # \xH not allowed
1508 _ysh-parse-error-here <<'EOF'
1509const bad = c'\xf'
1510EOF
1511}
1512
1513test-bug_1825_backslashes() {
1514 # Single backslash is accepted in OSH
1515 _osh-should-parse-here <<'EOF'
1516echo $'trailing\
1517'
1518EOF
1519
1520 # Double backslash is right in YSH
1521 _ysh-should-parse-here <<'EOF'
1522echo $'trailing\\
1523'
1524EOF
1525
1526 # Single backslash is wrong in YSH
1527 _ysh-parse-error-here <<'EOF'
1528echo $'trailing\
1529'
1530EOF
1531
1532 # Also in expression mode
1533 _ysh-parse-error-here <<'EOF'
1534setvar x = $'trailing\
1535'
1536EOF
1537}
1538
1539test-ysh_dq_strings() {
1540 # Double quoted is an error
1541 _osh-should-parse 'echo "\z"'
1542
1543 # status, sh, message
1544 _assert-sh-status 2 "$OSH" $0 \
1545 +O parse_backslash -n -c 'echo test-parse_backslash "\z"'
1546
1547 _ysh-parse-error 'echo "\z"' # not in Oil
1548 _ysh-parse-error 'const bad = "\z"' # not in expression mode
1549
1550 # C style escapes not respected
1551 _osh-should-parse 'echo "\u1234"' # ok in OSH
1552 _ysh-parse-error 'echo "\u1234"' # not in Oil
1553 _ysh-parse-error 'const bad = "\u1234"'
1554
1555 _osh-should-parse 'echo "`echo hi`"'
1556 _ysh-parse-error 'echo "`echo hi`"'
1557 _ysh-parse-error 'const bad = "`echo hi`"'
1558
1559 _ysh-parse-error 'setvar x = "\z"'
1560}
1561
1562test-ysh_bare_words() {
1563 _ysh-should-parse 'echo \$'
1564 _ysh-parse-error 'echo \z'
1565}
1566
1567test-parse_dollar() {
1568 # The right way:
1569 # echo \$
1570 # echo \$:
1571
1572 CASES=(
1573 'echo $' # lex_mode_e.ShCommand
1574 'echo $:'
1575
1576 'echo "$"' # lex_mode_e.DQ
1577 'echo "$:"'
1578
1579 'echo ${x:-$}' # lex_mode_e.VSub_ArgUnquoted
1580 'echo ${x:-$:}'
1581
1582 'echo "${x:-$}"' # lex_mode_e.VSub_DQ
1583 'echo "${x:-$:}"'
1584 )
1585 for c in "${CASES[@]}"; do
1586
1587 _osh-should-parse "$c"
1588
1589 # status, sh, message
1590 _assert-sh-status 2 "$OSH" $0 \
1591 +O test-parse_dollar -n -c "$c"
1592
1593 _ysh-parse-error "$c"
1594 done
1595}
1596
1597test-parse-dparen() {
1598 # Bash (( construct
1599 local bad
1600
1601 bad='((1 > 0 && 43 > 42))'
1602 _osh-should-parse "$bad"
1603 _ysh-parse-error "$bad"
1604
1605 bad='if ((1 > 0 && 43 > 42)); then echo yes; fi'
1606 _osh-should-parse "$bad"
1607 _ysh-parse-error "$bad"
1608
1609 bad='for ((x = 1; x < 5; ++x)); do echo $x; done'
1610 _osh-should-parse "$bad"
1611 _ysh-parse-error "$bad"
1612
1613 _ysh-should-parse 'if (1 > 0 and 43 > 42) { echo yes }'
1614
1615 # Accepted workaround: add space
1616 _ysh-should-parse 'if ( (1 > 0 and 43 > 42) ) { echo yes }'
1617}
1618
1619test-invalid_parens() {
1620
1621 # removed function sub syntax
1622 local s='write -- $f(x)'
1623 _osh-parse-error "$s"
1624 _ysh-parse-error "$s"
1625
1626 # requires test-parse_at
1627 local s='write -- @[sorted(x)]'
1628 _osh-parse-error "$s" # this is a parse error, but BAD message!
1629 _ysh-should-parse "$s"
1630
1631 local s='
1632f() {
1633 write -- @[sorted(x)]
1634}
1635'
1636 _osh-parse-error "$s"
1637 _ysh-should-parse "$s"
1638
1639 # Analogous bad bug
1640 local s='
1641f() {
1642 write -- @sorted (( z ))
1643}
1644'
1645 _osh-parse-error "$s"
1646}
1647
1648test-eggex() {
1649 _osh-should-parse '= /%start dot %end \n \u{ff}/'
1650 _osh-parse-error '= /%star dot %end \n/'
1651 _osh-parse-error '= /%start do %end \n/'
1652 _osh-parse-error '= /%start dot %end \z/'
1653 _osh-parse-error '= /%start dot %end \n \u{}/'
1654
1655 _osh-should-parse "= /['a'-'z']/"
1656 _osh-parse-error "= /['a'-'']/"
1657 _osh-parse-error "= /['a'-'zz']/"
1658
1659 _osh-parse-error '= /dot{N *} /'
1660
1661 # Could validate the Id.Expr_Name
1662 _osh-parse-error '= /dot{zzz *} /'
1663
1664 # This could be allowed, but currently isn't
1665 _osh-parse-error '= /dot{*} /'
1666}
1667
1668#
1669# Entry Points
1670#
1671
1672soil-run-py() {
1673 run-test-funcs
1674}
1675
1676soil-run-cpp() {
1677 ninja _bin/cxx-asan/osh
1678 OSH=_bin/cxx-asan/osh run-test-funcs
1679}
1680
1681run-for-release() {
1682 run-other-suite-for-release ysh-parse-errors run-test-funcs
1683}
1684
1685filename=$(basename $0)
1686if test $filename = 'ysh-parse-errors.sh'; then
1687 "$@"
1688fi
1689