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

1690 lines, 582 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-should-parse 'proc p { echo 1; proc f { echo f }; echo 2 }'
480 _ysh-should-parse 'func f() { echo 1; proc f { echo f }; echo 2 }'
481 _ysh-should-parse 'proc p { echo 1; func f() { echo f }; echo 2 }'
482 _ysh-should-parse '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 # Test the matrix of (proc, func) x (sh-func) and (sh-func) x (proc, func)
487 _ysh-parse-error 'proc p { echo 1; function f { echo f; }; echo 2 }'
488 _ysh-parse-error 'func outer() { function f { echo f } }'
489 _ysh-parse-error 'f() { echo 1; proc inner { echo inner; }; echo 2; }'
490 _ysh-parse-error 'f() { func inner() { var a = 1 } }'
491
492 # shell nesting is still allowed
493 _ysh-should-parse 'f() { echo 1; g() { echo g; }; echo 2; }'
494
495 _ysh-should-parse 'proc p() { shopt --unset errexit { false hi } }'
496}
497
498test-int-literals() {
499 _ysh-should-parse '= 42'
500 _ysh-should-parse '= 42_0'
501 _ysh-parse-error '= 42_'
502 _ysh-parse-error '= 42_0_'
503
504 # this is a var name
505 _ysh-should-parse '= _42'
506}
507
508test-float-literals() {
509 _ysh-should-parse '= 42.0'
510 _ysh-should-parse '= 42_0.0'
511 _ysh-parse-error '= 42_.0'
512
513 _ysh-parse-error '= 42.'
514 _ysh-parse-error '= .333'
515
516 _ysh-parse-error '= _42.0'
517}
518
519test-lhs-expr() {
520 _ysh-should-parse 'setvar x.y[z] = 42'
521 _ysh-should-parse 'setvar a[i][j] = 42'
522
523 _ysh-should-parse 'setvar a[i], d.key = 42, 43'
524 _ysh-parse-error 'setvar a[i], 3 = 42, 43'
525 _ysh-parse-error 'setvar a[i], {}["key"] = 42, 43'
526
527 _ysh-parse-error 'setvar x+y = 42'
528
529 # method call
530 _ysh-parse-error 'setvar x->y = 42'
531
532 # this is allowed
533 _ysh-should-parse 'setglobal a[{k:3}["k"]] = 42'
534
535 _ysh-parse-error 'setglobal {}["k"] = 42'
536 _ysh-parse-error 'setglobal [1,2][0] = 42'
537}
538
539test-destructure() {
540 _ysh-parse-error '
541 func f() {
542 const x, y = 3, 4
543
544 #setvar x = 5
545
546 setvar y = 6
547 }'
548
549 _ysh-parse-error '
550 func f() {
551 var x, y = 3, 4
552
553 var y = 6
554 }'
555
556 _ysh-parse-error '
557 func f() {
558 var x, y = 3, 4
559
560 const y = 6
561 }'
562}
563
564test-lazy-arg-list() {
565 _ysh-should-parse 'assert [42 === x]'
566
567 _ysh-should-parse 'assert [ 42 === x ]'
568 _ysh-should-parse 'assert [42, 43]'
569 _ysh-should-parse 'assert [42, named=true]'
570 _ysh-should-parse 'assert [42, named=true]; echo hi'
571
572 _ysh-should-parse 'assert [42, named=true] { echo hi }'
573
574 # Seems fine
575 _ysh-should-parse 'assert [42, named=true]{ echo hi }'
576
577 # I guess this legacy is still valid? Or disallow explicitly
578 _ysh-should-parse 'assert *.[ch]'
579 _ysh-should-parse 'assert 42[ch]'
580 _ysh-should-parse 'echo[]'
581
582 _ysh-parse-error 'assert [4'
583 _ysh-parse-error 'assert [ 4'
584
585 _ysh-should-parse 'json write (42) >out'
586
587 # I guess this is OK
588 _ysh-should-parse 'json write >out (42)'
589
590 # BUG
591 #_ysh-parse-error 'when (42) >out { echo hi }'
592
593 #_ysh-should-parse 'when (42) { echo hi } >out'
594
595 # How to support this? Maybe the CommandParser can test for i == 0 when it
596 # gets Op_LBracket
597
598 # legacy
599 _ysh-should-parse '[ x = y ]'
600}
601
602test-place-expr() {
603 _ysh-should-parse 'read (&x)'
604
605 # TODO: parse these into something
606 _ysh-parse-error 'read (&x[0])'
607 _ysh-parse-error 'read (&x[0][1])'
608
609 _ysh-parse-error 'read (&x.key.other)'
610
611 # This is a runtime error, not a parse time error
612 _ysh-should-parse 'read (&x + 1)'
613
614 _ysh-parse-error 'read (&42)'
615 _ysh-parse-error 'read (&+)'
616
617 # Place expressions aren't parenthesized expressions
618 _ysh-parse-error 'read (&(x))'
619}
620
621test-units-suffix() {
622 _ysh-parse-error '= 100 M M'
623
624 _ysh-parse-error '= 100 M; echo'
625 _ysh-parse-error '= 100 Mi; echo'
626
627 _ysh-parse-error '= 9.9 Mi; echo'
628
629 # This is confusing, could disallow, or just rely on users not to type it
630 _ysh-parse-error '= 9.9e-1 Mi; echo'
631
632 # I don't like this, but it follows lexing rules I guess
633 _ysh-parse-error '= 100Mi'
634
635 _ysh-parse-error '= [100 Mi, 200 Mi]'
636
637 _ysh-parse-error '= {[42 Ki]: 43 Ki}'
638}
639
640test-type-expr() {
641 # This is nicer
642 _ysh-should-parse 'var x: Int = f()'
643
644 # But colon is optional
645 _ysh-should-parse 'var x Int = f()'
646
647 # Colon is noisy here because we have semi-colons
648 _ysh-should-parse 'proc p (; x Int, y Int; ) { echo hi }'
649
650 _ysh-should-parse 'func f (x Int, y Int; z Int = 0) { echo hi }'
651
652 # Hm should these be allowed, but discouraged?
653 #_ysh-should-parse 'func f (x Int, y Int; z: Int = 0) { echo hi }'
654 #_ysh-should-parse 'proc p (; x: Int, y: Int;) { echo hi }'
655}
656
657test-no-const() {
658 _ysh-should-parse 'const x = 42'
659
660 # Must be at the top level
661 _ysh-parse-error '
662 proc p {
663 const x = 42
664 }'
665
666 _ysh-parse-error '
667 func f() {
668 const x = 42
669 }'
670}
671
672test-fat-arrow() {
673 _ysh-should-parse 'var x = s => trim()'
674 _ysh-should-parse 'func f(x Int) => List[Int] { echo hi }'
675}
676
677# Backslash in UNQUOTED context
678test-parse-backslash() {
679 _ysh-should-parse 'echo \('
680 _ysh-should-parse 'echo \;'
681 _ysh-should-parse 'echo ~'
682 _ysh-should-parse 'echo \!' # history?
683
684 _ysh-should-parse 'echo \%' # job ID? I feel like '%' is better
685 _ysh-should-parse 'echo \#' # comment
686
687 _ysh-parse-error 'echo \.'
688 _ysh-parse-error 'echo \-'
689 _ysh-parse-error 'echo \/'
690
691 _ysh-parse-error 'echo \a'
692 _ysh-parse-error 'echo \Z'
693 _ysh-parse-error 'echo \0'
694 _ysh-parse-error 'echo \9'
695
696 _osh-should-parse 'echo \. \- \/ \a \Z \0 \9'
697}
698
699test-make-these-nicer() {
700 # expects expression on right
701 _ysh-parse-error '='
702 _ysh-parse-error 'call'
703
704 # What about \u{123} parse errors
705 # I get a warning now, but parse_backslash should give a syntax error
706 # _ysh-parse-error "x = c'\\uz'"
707
708 # Dict pair split
709 _ysh-parse-error 'const d = { name:
71042 }'
711
712 #_ysh-parse-error ' d = %{}'
713}
714
715test-var-decl() {
716 _ysh-parse-error '
717 proc p(x) {
718 echo hi
719 var x = 2 # Cannot redeclare param
720 }
721 '
722
723 _ysh-parse-error '
724 proc p {
725 var x = 1
726 echo hi
727 var x = 2 # Cannot redeclare local
728 }
729 '
730
731 _ysh-parse-error '
732 proc p(x, :out) {
733 var out = 2 # Cannot redeclare out param
734 }
735 '
736
737 _ysh-parse-error '
738 proc p {
739 var out = 2 # Cannot redeclare out param
740 cd /tmp {
741 var out = 3
742 }
743 }
744 '
745
746 _ysh-should-parse '
747 var x = 1
748 proc p {
749 echo hi
750 var x = 2
751 }
752
753 proc p2 {
754 var x = 3
755 }
756 '
757}
758
759test-setvar() {
760 _ysh-should-parse '
761 proc p(x) {
762 var y = 1
763 setvar y = 42
764 }
765 '
766
767 _ysh-parse-error '
768 proc p(x) {
769 var y = 1
770 setvar L = "L" # ERROR: not declared
771 }
772 '
773
774 _ysh-parse-error '
775 proc p(x) {
776 var y = 1
777 setvar L[0] = "L" # ERROR: not declared
778 }
779 '
780
781 _ysh-parse-error '
782 proc p(x) {
783 var y = 1
784 setvar d.key = "v" # ERROR: not declared
785 }
786 '
787
788 _ysh-should-parse '
789 proc p(x) {
790 setvar x = "X" # is mutating params allowed? I guess why not.
791 }
792 '
793}
794
795test-ysh-case() {
796 _ysh-should-parse '
797 case (x) {
798 (else) { = 1; }
799 }
800 '
801
802 _ysh-should-parse '
803 var myexpr = ^[123]
804
805 case (123) {
806 (myexpr) { echo 1 }
807 }
808 '
809
810 _ysh-should-parse '
811 case (x) {
812 (else) { echo 1 }
813 }
814 '
815
816 _ysh-should-parse '
817 case (x) {
818 (else) { = 1 }
819 }
820 '
821
822 _ysh-should-parse '
823 case (x) {
824 (else) { = 1 }
825
826 }
827 '
828
829 _ysh-should-parse '
830 case (x) {
831 (else) { = 1 } # Comment
832 }
833 '
834
835 _ysh-should-parse '
836 case (3) {
837 (3) { echo hi }
838 # comment line
839 }
840 '
841
842 _ysh-should-parse '
843 case (x) {
844 (else) { echo 1 }
845 }
846 '
847
848 _ysh-should-parse '
849 case (foo) { (else) { echo } }
850 '
851
852 _ysh-should-parse '
853 case (foo) {
854 *.py { echo "python" }
855 }
856 '
857
858 _ysh-should-parse '
859 case (foo) {
860 (obj.attr) { echo "python" }
861 }
862 '
863
864 _ysh-should-parse '
865 case (foo) {
866 (0) { echo "python" }
867 }
868 '
869
870 _ysh-should-parse '
871 case (foo) {
872 ("main.py") { echo "python" }
873 }
874 '
875
876 # Various multi-line cases
877 if false; then # TODO: fixme, this is in the vein of the `if(x)` error
878 _ysh-should-parse '
879 case (foo){("main.py"){ echo "python" } }
880 '
881 fi
882 _ysh-should-parse '
883 case (foo) { ("main.py") { echo "python" } }
884 '
885 _ysh-should-parse '
886 case (foo) {
887 ("main.py") {
888 echo "python" } }'
889 _ysh-should-parse '
890 case (foo) {
891 ("main.py") {
892 echo "python" }
893 }
894 '
895 _ysh-should-parse '
896 case (foo) {
897 ("main.py") { echo "python"
898 }
899 }
900 '
901 _ysh-should-parse '
902 case (foo) {
903 ("main.py") {
904 echo "python"
905 }
906 }
907 '
908
909 # Example valid cases from grammar brain-storming
910 _ysh-should-parse '
911 case (add(10, 32)) {
912 (40 + 2) { echo Found the answer }
913 (else) { echo Incorrect
914 }
915 }
916 '
917
918 _ysh-should-parse "
919 case (file) {
920 / dot* '.py' / {
921 echo Python
922 }
923
924 / dot* ('.cc' | '.h') /
925 {
926 echo C++
927 }
928 }
929 "
930 _ysh-should-parse '
931 case (lang) {
932 en-US
933 | en-CA
934 | en-UK {
935 echo Hello
936 }
937 fr-FR |
938 fr-CA {
939 echo Bonjour
940 }
941
942
943
944
945
946 (else) {
947 echo o/
948 }
949 }
950 '
951
952 _ysh-should-parse '
953 case (num) {
954 (1) | (2) {
955 echo number
956 }
957 }
958 '
959
960 _ysh-should-parse '
961 case (num) {
962 (1) | (2) | (3)
963 | (4) | (5) {
964 echo small
965 }
966
967 (else) {
968 echo large
969 }
970 }
971 '
972
973 # Example invalid cases from grammar brain-storming
974 _ysh-parse-error '
975 case
976 (add(10, 32)) {
977 (40 + 2) { echo Found the answer }
978 (else) { echo Incorrect }
979 }
980 '
981 _ysh-parse-error "
982 case (file)
983 {
984 ('README') | / dot* '.md' / { echo Markdown }
985 }
986 "
987 _ysh-parse-error '
988 case (file)
989 {
990 {
991 echo Python
992 }
993 }
994 '
995 _ysh-parse-error '
996 case (file)
997 {
998 cc h {
999 echo C++
1000 }
1001 }
1002 '
1003 _ysh-parse-error "
1004 case (lang) {
1005 en-US
1006 | ('en-CA')
1007 | / 'en-UK' / {
1008 echo Hello
1009 }
1010 }
1011 "
1012 _ysh-parse-error '
1013 case (lang) {
1014 else) {
1015 echo o/
1016 }
1017 }
1018 '
1019 _ysh-parse-error '
1020 case (num) {
1021 (1) | (2) | (3)
1022 | (4) | (5) {
1023 echo small
1024 }
1025
1026 (6) | (else) {
1027 echo large
1028 }
1029 }
1030 '
1031
1032 _ysh-parse-error '
1033 case $foo {
1034 ("main.py") {
1035 echo "python"
1036 }
1037 }
1038 '
1039
1040 # Newline not allowed, because it isn't in for, if, while, etc.
1041 _ysh-parse-error '
1042 case (x)
1043 {
1044 *.py { echo "python" }
1045 }
1046 '
1047
1048 _ysh-parse-error '
1049 case (foo) in
1050 *.py {
1051 echo "python"
1052 }
1053 esac
1054 '
1055
1056 _ysh-parse-error '
1057 case $foo {
1058 bar) {
1059 echo "python"
1060 }
1061 }
1062 '
1063
1064 _ysh-parse-error '
1065 case (x) {
1066 {
1067 echo "python"
1068 }
1069 }
1070 '
1071
1072 _ysh-parse-error '
1073 case (x {
1074 *.py { echo "python" }
1075 }
1076 '
1077
1078 _ysh-parse-error '
1079 case (x) {
1080 *.py) { echo "python" }
1081 }
1082 '
1083
1084 _ysh-should-parse "case (x) { word { echo word; } (3) { echo expr; } /'eggex'/ { echo eggex; } }"
1085
1086 _ysh-should-parse "
1087case (x) {
1088 word { echo word; } (3) { echo expr; } /'eggex'/ { echo eggex; } }"
1089
1090 _ysh-should-parse "
1091case (x) {
1092 word { echo word; }
1093 (3) { echo expr; } /'eggex'/ { echo eggex; } }"
1094
1095 _ysh-should-parse "
1096case (x) {
1097 word { echo word; }
1098 (3) { echo expr; }
1099 /'eggex'/ { echo eggex; } }"
1100
1101 _ysh-should-parse "
1102case (x) {
1103 word { echo word; }
1104 (3) { echo expr; }
1105 /'eggex'/ { echo eggex; }
1106}"
1107
1108 # No leading space
1109 _ysh-should-parse "
1110case (x) {
1111word { echo word; }
1112(3) { echo expr; }
1113/'eggex'/ { echo eggex; }
1114}"
1115}
1116
1117test-ysh-for() {
1118 _ysh-should-parse '
1119 for x in (obj) {
1120 echo $x
1121 }
1122 '
1123
1124 _ysh-parse-error '
1125 for x in (obj); do
1126 echo $x
1127 done
1128 '
1129
1130 _ysh-should-parse '
1131 for x, y in SPAM EGGS; do
1132 echo $x
1133 done
1134 '
1135
1136 # Bad loop variable name
1137 _ysh-parse-error '
1138 for x-y in SPAM EGGS; do
1139 echo $x
1140 done
1141 '
1142
1143 # Too many indices
1144 _ysh-parse-error '
1145 for x, y, z in SPAM EGGS; do
1146 echo $x
1147 done
1148 '
1149
1150 _ysh-parse-error '
1151 for w, x, y, z in SPAM EGGS; do
1152 echo $x
1153 done
1154 '
1155
1156 # Old style
1157 _ysh-should-parse '
1158 for x, y in SPAM EGGS
1159 do
1160 echo $x
1161 done
1162 '
1163
1164 # for shell compatibility, allow this
1165 _ysh-should-parse 'for const in (x) { echo $var }'
1166}
1167
1168test-for-parse-bare-word() {
1169 _ysh-parse-error '
1170 for x in bare {
1171 echo $x
1172 }
1173 '
1174
1175 _ysh-should-parse '
1176 for x in a b {
1177 echo $x
1178 }
1179 '
1180
1181 _ysh-should-parse '
1182 for x in *.py {
1183 echo $x
1184 }
1185 '
1186
1187 _ysh-should-parse '
1188 for x in "quoted" {
1189 echo $x
1190 }
1191 '
1192}
1193
1194test-for() {
1195 # Technically we could switch to a different lexer mode here, but it seems
1196 # easy enough to reuse the Id.Redir_LessGreat token
1197 _ysh-parse-error '
1198 for x in <> {
1199 echo $x
1200 }
1201 '
1202
1203 _ysh-parse-error '
1204 for x in <>
1205 {
1206 echo $x
1207 }
1208 '
1209
1210 _ysh-parse-error '
1211 for x in < {
1212 echo $x
1213 }
1214 '
1215
1216 # Space between < >
1217 _ysh-parse-error '
1218 for x in < > {
1219 echo $x
1220 }
1221 '
1222}
1223
1224test-bug-1118() {
1225 # Originally pointed at 'for'
1226 _ysh-parse-error '
1227 var snippets = [{status: 42}]
1228 for snippet in (snippets) {
1229 if (snippet["status"] === 0) {
1230 echo hi
1231 }
1232
1233 # The $ causes a weird error
1234 if ($snippet["status"] === 0) {
1235 echo hi
1236 }
1237 }
1238 '
1239
1240 # Issue #1118
1241 # pointed at 'var' in count
1242 _ysh-parse-error '
1243 var content = [ 1, 2, 4 ]
1244 var count = 0
1245
1246 # The $ causes a weird error
1247 while (count < $len(content)) {
1248 setvar count += 1
1249 }
1250 '
1251}
1252
1253test-bug-1850() {
1254 _ysh-should-parse 'pp test_ (42); pp line (43)'
1255 #_osh-should-parse 'pp test_ (42); pp line (43)'
1256
1257 # Extra word is bad
1258 _ysh-parse-error 'pp test_ (42) extra'
1259
1260 # Bug -- newline or block should come after arg list
1261 _ysh-parse-error 'pp test_ (42), echo'
1262
1263 # This properly checks a similar error. It's in a word.
1264 _ysh-parse-error 'pp test_ @(echo), echo'
1265
1266 # Common cases
1267 _ysh-should-parse 'pp test_ (42)'
1268 _ysh-should-parse 'pp test_ (42) '
1269 _ysh-should-parse 'pp test_ (42);'
1270 _ysh-should-parse 'pp test_ (42) { echo hi }'
1271
1272 # Original bug
1273
1274 # Accidental comma instead of ;
1275 # Wow this is parsed horribly - (42) replaced (43)
1276 _ysh-parse-error 'pp test_ (42), pp line (43)'
1277}
1278
1279test-bug-1850-more() {
1280 ### Regression
1281
1282 _ysh-parse-error 'assert (42)extra'
1283 _ysh-parse-error 'assert (42) extra'
1284
1285 _ysh-parse-error 'assert [42]extra'
1286 _ysh-parse-error 'assert [42] extra'
1287}
1288
1289test-command-simple-more() {
1290 _ysh-should-parse 'foo=1'
1291
1292 _ysh-parse-error 'foo=1 >out (42)'
1293
1294 _ysh-parse-error 'foo=1 (42)'
1295
1296 _ysh-should-parse 'foo=1 cmd (42)'
1297
1298 _ysh-should-parse 'foo=1 cmd >out (42)'
1299}
1300
1301test-proc-args() {
1302 _osh-should-parse 'json write (x)'
1303
1304 _osh-should-parse 'echo $(json write (x))' # relies on lexer.PushHint()
1305
1306 # nested expr -> command -> expr
1307 _osh-should-parse 'var result = $(json write (x))'
1308
1309 _osh-should-parse 'json write (x, y); echo hi'
1310
1311 # named arg
1312 _osh-should-parse '
1313json write (x, name = "value")
1314echo hi
1315'
1316
1317 # with block on same line
1318 _ysh-should-parse 'json write (x) { echo hi }'
1319
1320 # with block
1321 _ysh-should-parse '
1322json write (x) {
1323 echo hi
1324}'
1325
1326 # multiple lines
1327 _osh-should-parse 'json write (
1328 x,
1329 y,
1330 z
1331 )'
1332
1333 # can't be empty
1334 _ysh-parse-error 'json write ()'
1335 _ysh-parse-error 'json write ( )'
1336
1337 # should have a space
1338 _ysh-parse-error 'json write(x)'
1339 _ysh-parse-error 'json write()'
1340 _ysh-parse-error 'f(x)' # test short name
1341}
1342
1343test-eggex-capture() {
1344 _ysh-should-parse '= / d+ /'
1345 #_ysh-should-parse '= / <d+ : date> /'
1346 _ysh-should-parse '= / < capture d+ as date > /'
1347 _ysh-should-parse '= / < capture d+ as date: Int > /'
1348
1349 # These keywords are taken in regular expressions, I guess that's OK.
1350 _ysh-parse-error 'var capture = 42'
1351 _ysh-parse-error 'var as = 42'
1352}
1353
1354
1355test-eggex-flags() {
1356 _ysh-should-parse '= / d+ ; reg_icase /'
1357 _ysh-should-parse '= / d+ ; i /' # shortcut
1358
1359 # can't negate these
1360 _ysh-parse-error '= / d+ ; !i /'
1361
1362 # typo should be parse error
1363 _ysh-parse-error '= / d+ ; reg_oops /'
1364
1365 # PCRE should not validate
1366 _ysh-should-parse '= / d+ ; !i; PCRE /'
1367 _ysh-should-parse '= / d+ ; reg_oops; PCRE /'
1368
1369 # ERE means is the default; it's POSIX ERE
1370 # Other option is PCRE
1371 _ysh-should-parse '= / d+ ; i reg_newline ; ERE /'
1372 _ysh-should-parse '= / d+ ; ; ERE /'
1373
1374 # trailing ; is OK
1375 _ysh-should-parse '= / d+ ; /'
1376
1377 # doesn't make sense
1378 _ysh-parse-error '= / d+ ; ; /'
1379 _ysh-parse-error '= / d+ ; ; ; /'
1380}
1381
1382test-string-literals() {
1383 _ysh-should-parse "echo r'hi';"
1384 #_ysh-parse-error "echo r'hi'bad"
1385
1386 _ysh-should-parse "echo u'hi'"
1387 _ysh-should-parse "(echo u'hi')"
1388
1389 _ysh-parse-error "echo b'hi'trailing"
1390 _ysh-parse-error "echo b'hi'#notcomment"
1391
1392 # This is valid shell, but not a comment
1393 _ysh-should-parse "echo 'hi'#notcomment"
1394
1395}
1396
1397test-multiline-string() {
1398 _ysh-should-parse "echo u'''
1399hi
1400'''
1401"
1402 _ysh-should-parse "echo b'''
1403hi
1404'''
1405"
1406
1407 _ysh-parse-error "echo b'''
1408hi
1409''
1410"
1411
1412 _ysh-parse-error "echo r'''
1413hi
1414'''bad
1415"
1416
1417 _ysh-parse-error "echo u'''
1418hi
1419'''bad
1420"
1421
1422 _ysh-parse-error 'echo """
1423hi
1424"""bad
1425'
1426}
1427
1428test-bug-1826() {
1429 #return
1430
1431 read -r code_str << 'EOF'
1432echo b'\xff'
1433EOF
1434
1435 _ysh-parse-error "$code_str"
1436
1437 read -r code_str << 'EOF'
1438var s = b'\xff'
1439EOF
1440
1441 _ysh-parse-error "$code_str"
1442
1443 # Backslash ending the file
1444
1445 read -r code_str << 'EOF'
1446echo b'\
1447EOF
1448 echo "[$code_str]"
1449
1450 _ysh-parse-error "$code_str"
1451
1452 read -r code_str << 'EOF'
1453var s = b'\
1454EOF
1455 echo "[$code_str]"
1456
1457 _ysh-parse-error "$code_str"
1458
1459 read -r code_str << 'EOF'
1460var s = $'\
1461EOF
1462 echo "[$code_str]"
1463
1464 _ysh-parse-error "$code_str"
1465}
1466
1467test-ysh_c_strings() {
1468 # bash syntax
1469 _osh-should-parse-here <<'EOF'
1470echo $'\u03bc'
1471EOF
1472
1473 # Extension not allowed
1474 _ysh-parse-error-here <<'EOF'
1475echo $'\u{03bc}'
1476EOF
1477
1478 # Bad syntax
1479 _ysh-parse-error-here <<'EOF'
1480echo $'\u{03bc'
1481EOF
1482
1483 # Expression mode
1484 _ysh-parse-error-here <<'EOF'
1485const bad = $'\u{03bc'
1486EOF
1487
1488 # Test single quoted
1489 _osh-should-parse-here <<'EOF'
1490echo $'\z'
1491EOF
1492 _ysh-parse-error-here <<'EOF'
1493echo $'\z'
1494EOF
1495 # Expression mode
1496 _ysh-parse-error-here <<'EOF'
1497const bad = $'\z'
1498EOF
1499
1500 # Octal not allowed
1501 _osh-should-parse-here <<'EOF'
1502echo $'\101'
1503EOF
1504 _ysh-parse-error-here <<'EOF'
1505const bad = $'\101'
1506EOF
1507
1508 # \xH not allowed
1509 _ysh-parse-error-here <<'EOF'
1510const bad = c'\xf'
1511EOF
1512}
1513
1514test-bug_1825_backslashes() {
1515 # Single backslash is accepted in OSH
1516 _osh-should-parse-here <<'EOF'
1517echo $'trailing\
1518'
1519EOF
1520
1521 # Double backslash is right in YSH
1522 _ysh-should-parse-here <<'EOF'
1523echo $'trailing\\
1524'
1525EOF
1526
1527 # Single backslash is wrong in YSH
1528 _ysh-parse-error-here <<'EOF'
1529echo $'trailing\
1530'
1531EOF
1532
1533 # Also in expression mode
1534 _ysh-parse-error-here <<'EOF'
1535setvar x = $'trailing\
1536'
1537EOF
1538}
1539
1540test-ysh_dq_strings() {
1541 # Double quoted is an error
1542 _osh-should-parse 'echo "\z"'
1543
1544 # status, sh, message
1545 _assert-sh-status 2 "$OSH" $0 \
1546 +O parse_backslash -n -c 'echo test-parse_backslash "\z"'
1547
1548 _ysh-parse-error 'echo "\z"' # not in Oil
1549 _ysh-parse-error 'const bad = "\z"' # not in expression mode
1550
1551 # C style escapes not respected
1552 _osh-should-parse 'echo "\u1234"' # ok in OSH
1553 _ysh-parse-error 'echo "\u1234"' # not in Oil
1554 _ysh-parse-error 'const bad = "\u1234"'
1555
1556 _osh-should-parse 'echo "`echo hi`"'
1557 _ysh-parse-error 'echo "`echo hi`"'
1558 _ysh-parse-error 'const bad = "`echo hi`"'
1559
1560 _ysh-parse-error 'setvar x = "\z"'
1561}
1562
1563test-ysh_bare_words() {
1564 _ysh-should-parse 'echo \$'
1565 _ysh-parse-error 'echo \z'
1566}
1567
1568test-parse_dollar() {
1569 # The right way:
1570 # echo \$
1571 # echo \$:
1572
1573 CASES=(
1574 'echo $' # lex_mode_e.ShCommand
1575 'echo $:'
1576
1577 'echo "$"' # lex_mode_e.DQ
1578 'echo "$:"'
1579
1580 'echo ${x:-$}' # lex_mode_e.VSub_ArgUnquoted
1581 'echo ${x:-$:}'
1582
1583 'echo "${x:-$}"' # lex_mode_e.VSub_DQ
1584 'echo "${x:-$:}"'
1585 )
1586 for c in "${CASES[@]}"; do
1587
1588 _osh-should-parse "$c"
1589
1590 # status, sh, message
1591 _assert-sh-status 2 "$OSH" $0 \
1592 +O test-parse_dollar -n -c "$c"
1593
1594 _ysh-parse-error "$c"
1595 done
1596}
1597
1598test-parse-dparen() {
1599 # Bash (( construct
1600 local bad
1601
1602 bad='((1 > 0 && 43 > 42))'
1603 _osh-should-parse "$bad"
1604 _ysh-parse-error "$bad"
1605
1606 bad='if ((1 > 0 && 43 > 42)); then echo yes; fi'
1607 _osh-should-parse "$bad"
1608 _ysh-parse-error "$bad"
1609
1610 bad='for ((x = 1; x < 5; ++x)); do echo $x; done'
1611 _osh-should-parse "$bad"
1612 _ysh-parse-error "$bad"
1613
1614 _ysh-should-parse 'if (1 > 0 and 43 > 42) { echo yes }'
1615
1616 # Accepted workaround: add space
1617 _ysh-should-parse 'if ( (1 > 0 and 43 > 42) ) { echo yes }'
1618}
1619
1620test-invalid_parens() {
1621
1622 # removed function sub syntax
1623 local s='write -- $f(x)'
1624 _osh-parse-error "$s"
1625 _ysh-parse-error "$s"
1626
1627 # requires test-parse_at
1628 local s='write -- @[sorted(x)]'
1629 _osh-parse-error "$s" # this is a parse error, but BAD message!
1630 _ysh-should-parse "$s"
1631
1632 local s='
1633f() {
1634 write -- @[sorted(x)]
1635}
1636'
1637 _osh-parse-error "$s"
1638 _ysh-should-parse "$s"
1639
1640 # Analogous bad bug
1641 local s='
1642f() {
1643 write -- @sorted (( z ))
1644}
1645'
1646 _osh-parse-error "$s"
1647}
1648
1649test-eggex() {
1650 _osh-should-parse '= /%start dot %end \n \u{ff}/'
1651 _osh-parse-error '= /%star dot %end \n/'
1652 _osh-parse-error '= /%start do %end \n/'
1653 _osh-parse-error '= /%start dot %end \z/'
1654 _osh-parse-error '= /%start dot %end \n \u{}/'
1655
1656 _osh-should-parse "= /['a'-'z']/"
1657 _osh-parse-error "= /['a'-'']/"
1658 _osh-parse-error "= /['a'-'zz']/"
1659
1660 _osh-parse-error '= /dot{N *} /'
1661
1662 # Could validate the Id.Expr_Name
1663 _osh-parse-error '= /dot{zzz *} /'
1664
1665 # This could be allowed, but currently isn't
1666 _osh-parse-error '= /dot{*} /'
1667}
1668
1669#
1670# Entry Points
1671#
1672
1673soil-run-py() {
1674 run-test-funcs
1675}
1676
1677soil-run-cpp() {
1678 ninja _bin/cxx-asan/osh
1679 OSH=_bin/cxx-asan/osh run-test-funcs
1680}
1681
1682run-for-release() {
1683 run-other-suite-for-release ysh-parse-errors run-test-funcs
1684}
1685
1686filename=$(basename $0)
1687if test $filename = 'ysh-parse-errors.sh'; then
1688 "$@"
1689fi
1690