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

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