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

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