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

1659 lines, 574 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-bug-1118() {
1194 # Originally pointed at 'for'
1195 _ysh-parse-error '
1196 var snippets = [{status: 42}]
1197 for snippet in (snippets) {
1198 if (snippet["status"] === 0) {
1199 echo hi
1200 }
1201
1202 # The $ causes a weird error
1203 if ($snippet["status"] === 0) {
1204 echo hi
1205 }
1206 }
1207 '
1208
1209 # Issue #1118
1210 # pointed at 'var' in count
1211 _ysh-parse-error '
1212 var content = [ 1, 2, 4 ]
1213 var count = 0
1214
1215 # The $ causes a weird error
1216 while (count < $len(content)) {
1217 setvar count += 1
1218 }
1219 '
1220}
1221
1222test-bug-1850() {
1223 _ysh-should-parse 'pp line (42); pp line (43)'
1224 #_osh-should-parse 'pp line (42); pp line (43)'
1225
1226 # Extra word is bad
1227 _ysh-parse-error 'pp line (42) extra'
1228
1229 # Bug -- newline or block should come after arg list
1230 _ysh-parse-error 'pp line (42), echo'
1231
1232 # This properly checks a similar error. It's in a word.
1233 _ysh-parse-error 'pp line @(echo), echo'
1234
1235 # Common cases
1236 _ysh-should-parse 'pp line (42)'
1237 _ysh-should-parse 'pp line (42) '
1238 _ysh-should-parse 'pp line (42);'
1239 _ysh-should-parse 'pp line (42) { echo hi }'
1240
1241 # Original bug
1242
1243 # Accidental comma instead of ;
1244 # Wow this is parsed horribly - (42) replaced (43)
1245 _ysh-parse-error 'pp line (42), pp line (43)'
1246}
1247
1248test-bug-1850-more() {
1249 ### Regression
1250
1251 _ysh-parse-error 'assert (42)extra'
1252 _ysh-parse-error 'assert (42) extra'
1253
1254 _ysh-parse-error 'assert [42]extra'
1255 _ysh-parse-error 'assert [42] extra'
1256}
1257
1258test-command-simple-more() {
1259 _ysh-should-parse 'foo=1'
1260
1261 _ysh-parse-error 'foo=1 >out (42)'
1262
1263 _ysh-parse-error 'foo=1 (42)'
1264
1265 _ysh-should-parse 'foo=1 cmd (42)'
1266
1267 _ysh-should-parse 'foo=1 cmd >out (42)'
1268}
1269
1270test-proc-args() {
1271 _osh-should-parse 'json write (x)'
1272
1273 _osh-should-parse 'echo $(json write (x))' # relies on lexer.PushHint()
1274
1275 # nested expr -> command -> expr
1276 _osh-should-parse 'var result = $(json write (x))'
1277
1278 _osh-should-parse 'json write (x, y); echo hi'
1279
1280 # named arg
1281 _osh-should-parse '
1282json write (x, name = "value")
1283echo hi
1284'
1285
1286 # with block on same line
1287 _ysh-should-parse 'json write (x) { echo hi }'
1288
1289 # with block
1290 _ysh-should-parse '
1291json write (x) {
1292 echo hi
1293}'
1294
1295 # multiple lines
1296 _osh-should-parse 'json write (
1297 x,
1298 y,
1299 z
1300 )'
1301
1302 # can't be empty
1303 _ysh-parse-error 'json write ()'
1304 _ysh-parse-error 'json write ( )'
1305
1306 # should have a space
1307 _ysh-parse-error 'json write(x)'
1308 _ysh-parse-error 'json write()'
1309 _ysh-parse-error 'f(x)' # test short name
1310}
1311
1312test-eggex-capture() {
1313 _ysh-should-parse '= / d+ /'
1314 #_ysh-should-parse '= / <d+ : date> /'
1315 _ysh-should-parse '= / < capture d+ as date > /'
1316 _ysh-should-parse '= / < capture d+ as date: Int > /'
1317
1318 # These keywords are taken in regular expressions, I guess that's OK.
1319 _ysh-parse-error 'var capture = 42'
1320 _ysh-parse-error 'var as = 42'
1321}
1322
1323
1324test-eggex-flags() {
1325 _ysh-should-parse '= / d+ ; reg_icase /'
1326 _ysh-should-parse '= / d+ ; i /' # shortcut
1327
1328 # can't negate these
1329 _ysh-parse-error '= / d+ ; !i /'
1330
1331 # typo should be parse error
1332 _ysh-parse-error '= / d+ ; reg_oops /'
1333
1334 # PCRE should not validate
1335 _ysh-should-parse '= / d+ ; !i; PCRE /'
1336 _ysh-should-parse '= / d+ ; reg_oops; PCRE /'
1337
1338 # ERE means is the default; it's POSIX ERE
1339 # Other option is PCRE
1340 _ysh-should-parse '= / d+ ; i reg_newline ; ERE /'
1341 _ysh-should-parse '= / d+ ; ; ERE /'
1342
1343 # trailing ; is OK
1344 _ysh-should-parse '= / d+ ; /'
1345
1346 # doesn't make sense
1347 _ysh-parse-error '= / d+ ; ; /'
1348 _ysh-parse-error '= / d+ ; ; ; /'
1349}
1350
1351test-string-literals() {
1352 _ysh-should-parse "echo r'hi';"
1353 #_ysh-parse-error "echo r'hi'bad"
1354
1355 _ysh-should-parse "echo u'hi'"
1356 _ysh-should-parse "(echo u'hi')"
1357
1358 _ysh-parse-error "echo b'hi'trailing"
1359 _ysh-parse-error "echo b'hi'#notcomment"
1360
1361 # This is valid shell, but not a comment
1362 _ysh-should-parse "echo 'hi'#notcomment"
1363
1364}
1365
1366test-multiline-string() {
1367 _ysh-should-parse "echo u'''
1368hi
1369'''
1370"
1371 _ysh-should-parse "echo b'''
1372hi
1373'''
1374"
1375
1376 _ysh-parse-error "echo b'''
1377hi
1378''
1379"
1380
1381 _ysh-parse-error "echo r'''
1382hi
1383'''bad
1384"
1385
1386 _ysh-parse-error "echo u'''
1387hi
1388'''bad
1389"
1390
1391 _ysh-parse-error 'echo """
1392hi
1393"""bad
1394'
1395}
1396
1397test-bug-1826() {
1398 #return
1399
1400 read -r code_str << 'EOF'
1401echo b'\xff'
1402EOF
1403
1404 _ysh-parse-error "$code_str"
1405
1406 read -r code_str << 'EOF'
1407var s = b'\xff'
1408EOF
1409
1410 _ysh-parse-error "$code_str"
1411
1412 # Backslash ending the file
1413
1414 read -r code_str << 'EOF'
1415echo b'\
1416EOF
1417 echo "[$code_str]"
1418
1419 _ysh-parse-error "$code_str"
1420
1421 read -r code_str << 'EOF'
1422var s = b'\
1423EOF
1424 echo "[$code_str]"
1425
1426 _ysh-parse-error "$code_str"
1427
1428 read -r code_str << 'EOF'
1429var s = $'\
1430EOF
1431 echo "[$code_str]"
1432
1433 _ysh-parse-error "$code_str"
1434}
1435
1436test-ysh_c_strings() {
1437 # bash syntax
1438 _osh-should-parse-here <<'EOF'
1439echo $'\u03bc'
1440EOF
1441
1442 # Extension not allowed
1443 _ysh-parse-error-here <<'EOF'
1444echo $'\u{03bc}'
1445EOF
1446
1447 # Bad syntax
1448 _ysh-parse-error-here <<'EOF'
1449echo $'\u{03bc'
1450EOF
1451
1452 # Expression mode
1453 _ysh-parse-error-here <<'EOF'
1454const bad = $'\u{03bc'
1455EOF
1456
1457 # Test single quoted
1458 _osh-should-parse-here <<'EOF'
1459echo $'\z'
1460EOF
1461 _ysh-parse-error-here <<'EOF'
1462echo $'\z'
1463EOF
1464 # Expression mode
1465 _ysh-parse-error-here <<'EOF'
1466const bad = $'\z'
1467EOF
1468
1469 # Octal not allowed
1470 _osh-should-parse-here <<'EOF'
1471echo $'\101'
1472EOF
1473 _ysh-parse-error-here <<'EOF'
1474const bad = $'\101'
1475EOF
1476
1477 # \xH not allowed
1478 _ysh-parse-error-here <<'EOF'
1479const bad = c'\xf'
1480EOF
1481}
1482
1483test-bug_1825_backslashes() {
1484 # Single backslash is accepted in OSH
1485 _osh-should-parse-here <<'EOF'
1486echo $'trailing\
1487'
1488EOF
1489
1490 # Double backslash is right in YSH
1491 _ysh-should-parse-here <<'EOF'
1492echo $'trailing\\
1493'
1494EOF
1495
1496 # Single backslash is wrong in YSH
1497 _ysh-parse-error-here <<'EOF'
1498echo $'trailing\
1499'
1500EOF
1501
1502 # Also in expression mode
1503 _ysh-parse-error-here <<'EOF'
1504setvar x = $'trailing\
1505'
1506EOF
1507}
1508
1509test-ysh_dq_strings() {
1510 # Double quoted is an error
1511 _osh-should-parse 'echo "\z"'
1512
1513 # status, sh, message
1514 _assert-sh-status 2 "$OSH" $0 \
1515 +O parse_backslash -n -c 'echo test-parse_backslash "\z"'
1516
1517 _ysh-parse-error 'echo "\z"' # not in Oil
1518 _ysh-parse-error 'const bad = "\z"' # not in expression mode
1519
1520 # C style escapes not respected
1521 _osh-should-parse 'echo "\u1234"' # ok in OSH
1522 _ysh-parse-error 'echo "\u1234"' # not in Oil
1523 _ysh-parse-error 'const bad = "\u1234"'
1524
1525 _osh-should-parse 'echo "`echo hi`"'
1526 _ysh-parse-error 'echo "`echo hi`"'
1527 _ysh-parse-error 'const bad = "`echo hi`"'
1528
1529 _ysh-parse-error 'setvar x = "\z"'
1530}
1531
1532test-ysh_bare_words() {
1533 _ysh-should-parse 'echo \$'
1534 _ysh-parse-error 'echo \z'
1535}
1536
1537test-parse_dollar() {
1538 # The right way:
1539 # echo \$
1540 # echo \$:
1541
1542 CASES=(
1543 'echo $' # lex_mode_e.ShCommand
1544 'echo $:'
1545
1546 'echo "$"' # lex_mode_e.DQ
1547 'echo "$:"'
1548
1549 'echo ${x:-$}' # lex_mode_e.VSub_ArgUnquoted
1550 'echo ${x:-$:}'
1551
1552 'echo "${x:-$}"' # lex_mode_e.VSub_DQ
1553 'echo "${x:-$:}"'
1554 )
1555 for c in "${CASES[@]}"; do
1556
1557 _osh-should-parse "$c"
1558
1559 # status, sh, message
1560 _assert-sh-status 2 "$OSH" $0 \
1561 +O test-parse_dollar -n -c "$c"
1562
1563 _ysh-parse-error "$c"
1564 done
1565}
1566
1567test-parse-dparen() {
1568 # Bash (( construct
1569 local bad
1570
1571 bad='((1 > 0 && 43 > 42))'
1572 _osh-should-parse "$bad"
1573 _ysh-parse-error "$bad"
1574
1575 bad='if ((1 > 0 && 43 > 42)); then echo yes; fi'
1576 _osh-should-parse "$bad"
1577 _ysh-parse-error "$bad"
1578
1579 bad='for ((x = 1; x < 5; ++x)); do echo $x; done'
1580 _osh-should-parse "$bad"
1581 _ysh-parse-error "$bad"
1582
1583 _ysh-should-parse 'if (1 > 0 and 43 > 42) { echo yes }'
1584
1585 # Accepted workaround: add space
1586 _ysh-should-parse 'if ( (1 > 0 and 43 > 42) ) { echo yes }'
1587}
1588
1589test-invalid_parens() {
1590
1591 # removed function sub syntax
1592 local s='write -- $f(x)'
1593 _osh-parse-error "$s"
1594 _ysh-parse-error "$s"
1595
1596 # requires test-parse_at
1597 local s='write -- @[sorted(x)]'
1598 _osh-parse-error "$s" # this is a parse error, but BAD message!
1599 _ysh-should-parse "$s"
1600
1601 local s='
1602f() {
1603 write -- @[sorted(x)]
1604}
1605'
1606 _osh-parse-error "$s"
1607 _ysh-should-parse "$s"
1608
1609 # Analogous bad bug
1610 local s='
1611f() {
1612 write -- @sorted (( z ))
1613}
1614'
1615 _osh-parse-error "$s"
1616}
1617
1618test-eggex() {
1619 _osh-should-parse '= /%start dot %end \n \u{ff}/'
1620 _osh-parse-error '= /%star dot %end \n/'
1621 _osh-parse-error '= /%start do %end \n/'
1622 _osh-parse-error '= /%start dot %end \z/'
1623 _osh-parse-error '= /%start dot %end \n \u{}/'
1624
1625 _osh-should-parse "= /['a'-'z']/"
1626 _osh-parse-error "= /['a'-'']/"
1627 _osh-parse-error "= /['a'-'zz']/"
1628
1629 _osh-parse-error '= /dot{N *} /'
1630
1631 # Could validate the Id.Expr_Name
1632 _osh-parse-error '= /dot{zzz *} /'
1633
1634 # This could be allowed, but currently isn't
1635 _osh-parse-error '= /dot{*} /'
1636}
1637
1638#
1639# Entry Points
1640#
1641
1642soil-run-py() {
1643 run-test-funcs
1644}
1645
1646soil-run-cpp() {
1647 ninja _bin/cxx-asan/osh
1648 OSH=_bin/cxx-asan/osh run-test-funcs
1649}
1650
1651run-for-release() {
1652 run-other-suite-for-release ysh-parse-errors run-test-funcs
1653}
1654
1655filename=$(basename $0)
1656if test $filename = 'ysh-parse-errors.sh'; then
1657 "$@"
1658fi
1659