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

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