dnl PSPP - a program for statistical analysis.
dnl Copyright (C) 2017 Free Software Foundation, Inc.
dnl
dnl This program is free software: you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation, either version 3 of the License, or
dnl (at your option) any later version.
dnl
dnl This program is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
dnl GNU nGeneral Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with this program.  If not, see <http://www.gnu.org/licenses/>.
dnl
AT_BANNER([DEFINE])

AT_SETUP([simple macro expansion])
AT_DATA([define.sps], [dnl
DEFINE !macro()
a b c d
e f g h.
i j k l
1,2,3,4.
5+6+7.
m(n,o).
"a" "b" "c" 'a' 'b' 'c'.
"x "" y".
!ENDDEFINE.
DEBUG EXPAND.
!macro
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
a b c d e f g h.
i j k l 1, 2, 3, 4.
5 + 6 + 7.
m(n, o).
"a" "b" "c" 'a' 'b' 'c'.
"x "" y".
])
AT_CLEANUP

AT_SETUP([redefining a macro])
AT_DATA([define.sps], [dnl
DEFINE !macro() 0 !ENDDEFINE.
DEFINE !macro() 1 !ENDDEFINE.
DEBUG EXPAND.
!macro.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
1
])
AT_CLEANUP

AT_SETUP([macro expansion - one !TOKENS(1) positional argument])
AT_KEYWORDS([TOKENS])
AT_DATA([define.sps], [dnl
DEFINE !t1(!positional=!tokens(1)) t1 (!1) !ENDDEFINE.
DEBUG EXPAND.
!t1 a.
!t1 b.
!t1 a b.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
t1(a)

t1(b)

t1(a)

note: unexpanded token "b"
])
AT_CLEANUP

AT_SETUP([macro expansion with positional arguments])
AT_DATA([define.sps], [dnl
DEFINE !title(!positional !tokens(1)) !1 !ENDDEFINE.
DEFINE !t1(!positional !tokens(1)) t1 (!1) !ENDDEFINE.
DEFINE !t2(!positional !tokens(2)) t2 (!1) !ENDDEFINE.

DEFINE !ce(!positional=!charend('/')) ce (!1) !ENDDEFINE.
DEFINE !ce2(!positional=!charend('(')
           /!positional !charend(')'))
ce2 (!1, !2)
!ENDDEFINE.

DEFINE !e(!positional !enclose('{','}')) e (!1) !ENDDEFINE.

DEFINE !cmd(!positional !cmdend) cmd(!1) !ENDDEFINE.
DEFINE !cmd2(!positional !cmdend
            /!positional !tokens(1))
cmd2(!1, !2)
!ENDDEFINE.

DEFINE !p(!positional !tokens(1)
         /!positional !tokens(1)
	 /!positional !tokens(1))
p(!1, !2, !3)(!*)
!ENDDEFINE.

DEBUG EXPAND.
!title "!TOKENS(1) argument."
!t1 a.
!t1 b.
!t1 a b.

!title "!TOKENS(2) argument."
!t2 a b.
!t2 b c d.

!title "!CHAREND argument."
!ce/.
!ce x/.
!ce x y/.
!ce x y z/.

!title "Two !CHAREND arguments."
!ce2 x(y).
!ce2 1 2 3 4().

!title "!ENCLOSE argument."
!e {}.
!e {a}.
!e {a b}.

!title "!CMDEND argument."
!cmd 1 2 3 4.
!cmd2 5 6.
7.

!title "Three !TOKENS(1) arguments."
!p a b c.
!p 1 -2 -3.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
"!TOKENS(1) argument."

t1(a)

t1(b)

t1(a)

note: unexpanded token "b"

"!TOKENS(2) argument."

t2(a b)

t2(b c)

note: unexpanded token "d"

"!CHAREND argument."

ce( )

ce(x)

ce(x y)

ce(x y z)

"Two !CHAREND arguments."

ce2(x, y)

ce2(1 2 3 4, )

"!ENCLOSE argument."

e( )

e(a)

e(a b)

"!CMDEND argument."

cmd(1 2 3 4)

cmd2(5 6, )

note: unexpanded token "7"

"Three !TOKENS(1) arguments."

p(a, b, c) (a b c)

p(1, -2, -3) (1 -2 -3)
])
AT_CLEANUP

AT_SETUP([macro call missing positional !TOKENS arguments])
AT_KEYWORDS([TOKENS])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !tokens(1) !default(x)
         /!positional !tokens(1) !default(y)
	 /!positional !tokens(1) !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p a b c.
!p a b.
!p a.
!p.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
(a, b, c)

(a, b, z)

(a, y, z)

(x, y, z)
])
AT_CLEANUP

AT_SETUP([macro call incomplete positional !TOKENS arguments])
AT_KEYWORDS([TOKENS])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !tokens(2) !default(x)
         /!positional !tokens(2) !default(y)
	 /!positional !tokens(2) !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p a1 a2 b1 b2 c1 c2.
!p a1 a2 b1 b2 c1.
!p a1 a2 b1 b2.
!p a1 a2 b1.
!p a1 a2.
!p a1.
!p.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
(a1 a2, b1 b2, c1 c2)

define.sps:8.18: error: DEBUG EXPAND: Reached end of command expecting 1 more
token in argument !3 to macro !p.
    8 | !p a1 a2 b1 b2 c1.
      |                  ^

(a1 a2, b1 b2, c1)

(a1 a2, b1 b2, z)

define.sps:10.12: error: DEBUG EXPAND: Reached end of command expecting 1 more
token in argument !2 to macro !p.
   10 | !p a1 a2 b1.
      |            ^

(a1 a2, b1, z)

(a1 a2, y, z)

define.sps:12.6: error: DEBUG EXPAND: Reached end of command expecting 1 more
token in argument !1 to macro !p.
   12 | !p a1.
      |      ^

(a1, y, z)

(x, y, z)
])
AT_CLEANUP

AT_SETUP([macro call empty positional !CHAREND arguments])
AT_KEYWORDS([CHAREND])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !charend(',') !default(x)
         /!positional !charend(';') !default(y)
	 /!positional !charend(':') !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p a,b;c:.
!p a,b;:.
!p a,;c:.
!p a,;:.
!p,b;c:.
!p,b;:.
!p,;c:.
!p,;:.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
(a, b, c)

(a, b, )

(a, , c)

(a, , )

(, b, c)

(, b, )

(, , c)

(, , )
])
AT_CLEANUP

AT_SETUP([macro call missing positional !CHAREND arguments])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !charend(',') !default(x)
         /!positional !charend(';') !default(y)
	 /!positional !charend(':') !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p a,b;c:.
!p a,b;.
!p a,;.
!p ,b;.
!p ,;.

!p a,.
!p ,.

!p.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
(a, b, c)

(a, b, z)

(a, , z)

(, b, z)

(, , z)

(a, y, z)

(, y, z)

(x, y, z)
])
AT_CLEANUP

AT_SETUP([macro call incomplete positional !CHAREND arguments])
AT_KEYWORDS([CHAREND])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !charend(',') !default(x)
         /!positional !charend(';') !default(y)
	 /!positional !charend(':') !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p a,b;c:.
!p a,b;c.
!p a,b;.
!p a,b.
!p a,.
!p a.
!p.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
(a, b, c)

define.sps:8.9: error: DEBUG EXPAND: Reached end of command expecting ":" in
argument !3 to macro !p.
    8 | !p a,b;c.
      |         ^

(a, b, c)

(a, b, z)

define.sps:10.7: error: DEBUG EXPAND: Reached end of command expecting ";" in
argument !2 to macro !p.
   10 | !p a,b.
      |       ^

(a, b, z)

(a, y, z)

define.sps:12.5: error: DEBUG EXPAND: Reached end of command expecting "," in
argument !1 to macro !p.
   12 | !p a.
      |     ^

(a, y, z)

(x, y, z)
])
AT_CLEANUP

AT_SETUP([macro call missing positional !ENCLOSE arguments])
AT_KEYWORDS([ENCLOSE])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !enclose('(',')') !default(x)
         /!positional !enclose('<','>') !default(y)
	 /!positional !enclose('{','}') !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p (a)<b>{c}.
!p (a)<b>.
!p (a).
!p.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
(a, b, c)

(a, b, z)

(a, y, z)

(x, y, z)
])
AT_CLEANUP

AT_SETUP([macro call incomplete positional !ENCLOSE arguments])
AT_KEYWORDS([ENCLOSE])
AT_DATA([define.sps], [dnl
DEFINE !p(!positional !enclose('(',')') !default(x)
         /!positional !enclose('<','>') !default(y)
	 /!positional !enclose('{','}') !default(z))
(!1, !2, !3)
!ENDDEFINE.
DEBUG EXPAND.
!p (a)<b>{c}.
!p (a)<b>{c.
!p (a)<b>{.
!p (a)<b>.
!p (a)<b.
!p (a)<.
!p (a).
!p (a.
!p (.
!p.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
(a, b, c)

define.sps:8.12: error: DEBUG EXPAND: Reached end of command expecting "}" in
argument !3 to macro !p.
    8 | !p (a)<b>{c.
      |            ^

(a, b, c)

define.sps:9.11: error: DEBUG EXPAND: Reached end of command expecting "}" in
argument !3 to macro !p.
    9 | !p (a)<b>{.
      |           ^

(a, b, )

(a, b, z)

define.sps:11.9: error: DEBUG EXPAND: Reached end of command expecting ">" in
argument !2 to macro !p.
   11 | !p (a)<b.
      |         ^

(a, b, z)

define.sps:12.8: error: DEBUG EXPAND: Reached end of command expecting ">" in
argument !2 to macro !p.
   12 | !p (a)<.
      |        ^

(a, , z)

(a, y, z)

define.sps:14.6: error: DEBUG EXPAND: Reached end of command expecting ")" in
argument !1 to macro !p.
   14 | !p (a.
      |      ^

(a, y, z)

define.sps:15.5: error: DEBUG EXPAND: Reached end of command expecting ")" in
argument !1 to macro !p.
   15 | !p (.
      |     ^

(, y, z)

(x, y, z)
])
AT_CLEANUP

AT_SETUP([keyword macro argument name with ! prefix])
AT_DATA([define.sps], [dnl
DEFINE !macro(!x !TOKENS(1).
])
AT_CHECK([pspp -O format=csv define.sps], [1], [dnl
"define.sps:1.15-1.16: error: DEFINE: Keyword macro parameter must be named in definition without ""!"" prefix.
    1 | DEFINE !macro@{:@!x !TOKENS(1).
      |               ^~"
])
AT_CLEANUP

AT_SETUP([reserved macro keyword argument name])
AT_DATA([define.sps], [dnl
DEFINE !macro(if=!TOKENS(1).
])
AT_CHECK([pspp -O format=csv define.sps], [1], [dnl
"define.sps:1.15-1.16: error: DEFINE: Cannot use macro keyword ""if"" as an argument name.
    1 | DEFINE !macro@{:@if=!TOKENS(1).
      |               ^~"
])
AT_CLEANUP

AT_SETUP([macro expansion - one !TOKENS(1) keyword argument])
AT_KEYWORDS([TOKENS])
AT_DATA([define.sps], [dnl
DEFINE !k(arg1 = !TOKENS(1)) k(!arg1) !ENDDEFINE.
DEBUG EXPAND.
!k arg1=x.
!k arg1=x y.
!k.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
k(x)

k(x)

note: unexpanded token "y"

k( )
])
AT_CLEANUP

AT_SETUP([macro expansion - one !TOKENS(1) keyword argument - negative])
AT_KEYWORDS([TOKENS])
AT_DATA([define.sps], [dnl
DEFINE !k(arg1 !TOKENS(1)) k(!arg1) !ENDDEFINE.
DEBUG EXPAND.
!k arg1.
!k arg1=.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:3.8: error: DEBUG EXPAND: Found `.' while expecting `=' reading
argument !arg1 to macro !k.
    3 | !k arg1.
      |        ^

k( )

define.sps:4.9: error: DEBUG EXPAND: Reached end of command expecting 1 more
token in argument !arg1 to macro !k.
    4 | !k arg1=.
      |         ^

k( )
])
AT_CLEANUP

AT_SETUP([macro expansion - !CHAREND keyword arguments])
AT_KEYWORDS([CHAREND])
AT_DATA([define.sps], [dnl
DEFINE !k(arg1 = !CHAREND('/')
         /arg2 = !CHAREND('/'))
k(!arg1, !arg2)
!ENDDEFINE.
DEBUG EXPAND.
!k arg1=x/ arg2=y/.
!k arg1=x/.
!k arg2=y/.
!k.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
k(x, y)

k(x, )

k(, y)

k(, )
])
AT_CLEANUP

AT_SETUP([macro expansion - !CHAREND keyword arguments - negative])
AT_KEYWORDS([CHAREND])
AT_DATA([define.sps], [dnl
DEFINE !k(arg1 = !CHAREND('/')
         /arg2 = !CHAREND('/'))
k(!arg1, !arg2)
!ENDDEFINE.
DEBUG EXPAND.
!k arg1.
!k arg1=.
!k arg1=x.
!k arg1=x/ arg2=y.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:6.8: error: DEBUG EXPAND: Found `.' while expecting `=' reading
argument !arg1 to macro !k.
    6 | !k arg1.
      |        ^

k(, )

define.sps:7.9: error: DEBUG EXPAND: Reached end of command expecting "/" in
argument !arg1 to macro !k.
    7 | !k arg1=.
      |         ^

k(, )

define.sps:8.10: error: DEBUG EXPAND: Reached end of command expecting "/" in
argument !arg1 to macro !k.
    8 | !k arg1=x.
      |          ^

k(x, )

define.sps:9.18: error: DEBUG EXPAND: Reached end of command expecting "/" in
argument !arg2 to macro !k.
    9 | !k arg1=x/ arg2=y.
      |                  ^

k(x, y)
])
AT_CLEANUP

AT_SETUP([macro expansion - !ENCLOSE keyword arguments])
AT_KEYWORDS([ENCLOSE])
AT_DATA([define.sps], [dnl
DEFINE !k(arg1 = !ENCLOSE('(',')')
         /arg2 = !ENCLOSE('{','}'))
k(!arg1, !arg2)
!ENDDEFINE.
DEBUG EXPAND.
!k arg1=(x) arg2={y}.
!k arg1=(x).
!k arg2={y}.
!k.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
k(x, y)

k(x, )

k(, y)

k(, )
])
AT_CLEANUP

AT_SETUP([macro expansion - !ENCLOSE keyword arguments - negative])
AT_KEYWORDS([ENCLOSE])
AT_DATA([define.sps], [dnl
DEFINE !k(arg1 = !ENCLOSE('(',')')
         /arg2 = !ENCLOSE('{','}'))
k(!arg1, !arg2)
!ENDDEFINE.
DEBUG EXPAND.
!k arg1.
!k arg1=.
!k arg1=x.
!k arg1=(x.
!k arg1=(x) arg2.
!k arg1=(x) arg2=.
!k arg1=(x) arg2=y.
!k arg1=(x) arg2=(y.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:6.8: error: DEBUG EXPAND: Found `.' while expecting `=' reading
argument !arg1 to macro !k.
    6 | !k arg1.
      |        ^

k(, )

define.sps:7.9: error: DEBUG EXPAND: Found `.' while expecting `@{:@' reading
argument !arg1 to macro !k.
    7 | !k arg1=.
      |         ^

k(, )

define.sps:8.9: error: DEBUG EXPAND: Found `x' while expecting `@{:@' reading
argument !arg1 to macro !k.
    8 | !k arg1=x.
      |         ^

k(, )

note: unexpanded token "x"

define.sps:9.11: error: DEBUG EXPAND: Reached end of command expecting "@:}@" in
argument !arg1 to macro !k.
    9 | !k arg1=@{:@x.
      |           ^

k(x, )

define.sps:10.17: error: DEBUG EXPAND: Found `.' while expecting `=' reading
argument !arg2 to macro !k.
   10 | !k arg1=(x) arg2.
      |                 ^

k(x, )

define.sps:11.18: error: DEBUG EXPAND: Found `.' while expecting `{' reading
argument !arg2 to macro !k.
   11 | !k arg1=(x) arg2=.
      |                  ^

k(x, )

define.sps:12.18: error: DEBUG EXPAND: Found `y' while expecting `{' reading
argument !arg2 to macro !k.
   12 | !k arg1=(x) arg2=y.
      |                  ^

k(x, )

note: unexpanded token "y"

define.sps:13.18: error: DEBUG EXPAND: Found `@{:@' while expecting `{' reading
argument !arg2 to macro !k.
   13 | !k arg1=(x) arg2=@{:@y.
      |                  ^

k(x, )

note: unexpanded token "@{:@"

note: unexpanded token "y"
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !BLANKS in the manual.
AT_SETUP([macro expansion - !BLANKS])
AT_KEYWORDS([BLANKS])
AT_DATA([define.sps], [dnl
DEFINE !b()
!BLANKS(0).
!QUOTE(!BLANKS(0)).
!BLANKS(1).
!QUOTE(!BLANKS(1)).
!BLANKS(2).
!QUOTE(!BLANKS(2)).
!BLANKS(5).
!QUOTE(!BLANKS(5)).
!ENDDEFINE.
DEBUG EXPAND.
!b.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
.
''.
.
' '.
.
'  '.
.
'     '.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !CONCAT in the manual.
AT_SETUP([macro expansion - !CONCAT])
AT_KEYWORDS([CONCAT])
AT_DATA([define.sps], [dnl
DEFINE !c()
!CONCAT(x, y).
!CONCAT('x', 'y').
!CONCAT(12, 34).
!CONCAT(!NULL, 123).
!CONCAT(x, 0).
!CONCAT(x, 0, y).
!CONCAT(0, x).
!CONCAT(0, x, y).
!ENDDEFINE.
DEBUG EXPAND.
!c
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
xy.
xy.
1234.
123.
x0.
x0y.
0 x.
0 xy.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !EVAL in the manual.
AT_SETUP([macro expansion - !EVAL])
AT_KEYWORDS([EVAL])
AT_DATA([define.sps], [dnl
DEFINE !vars() a b c !ENDDEFINE.

DEFINE !e()
!vars.
!QUOTE(!vars).
!EVAL(!vars).
!QUOTE(!EVAL(!vars)).
!ENDDEFINE

DEFINE !e2(!positional !enclose('(',')'))
!1.
!QUOTE(!1).
!EVAL(!1).
!QUOTE(!EVAL(!1)).
!ENDDEFINE.
DEBUG EXPAND.
!e.
!e2(!vars).
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
a b c.
'!vars'.
a b c.
'a b c'.

a b c.
'!vars'.
a b c.
'a b c'.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !HEAD in the manual.
AT_SETUP([macro expansion - !HEAD])
AT_KEYWORDS([HEAD])
AT_DATA([define.sps], [dnl
DEFINE !h()
!HEAD('a b c').
!HEAD('a').
!HEAD(!NULL).
!HEAD('').
!ENDDEFINE.
DEBUG EXPAND.
!h.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
a.
a.
.
.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !TAIL in the manual.
AT_SETUP([macro expansion - !TAIL])
AT_KEYWORDS([TAIL])
AT_DATA([define.sps], [dnl
DEFINE !t()
!TAIL('a b c').
!TAIL('a').
!TAIL(!NULL).
!TAIL('').
!ENDDEFINE.
DEBUG EXPAND.
!t.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
b c.
.
.
.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !INDEX in the manual.
AT_SETUP([macro expansion - !INDEX])
AT_KEYWORDS([INDEX])
AT_DATA([define.sps], [dnl
DEFINE !i()
!INDEX(banana, an).
!INDEX(banana, nan).
!INDEX(banana, apple).
!INDEX("banana", nan).
!INDEX("banana", "nan").
!INDEX(!UNQUOTE("banana"), !UNQUOTE("nan")).
!ENDDEFINE.
DEBUG EXPAND.
!i.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
2.
3.
0.
4.
0.
3.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !LENGTH in the manual.
AT_SETUP([macro expansion - !LENGTH])
AT_KEYWORDS([LENGTH])
AT_DATA([define.sps], [dnl
DEFINE !l()
!LENGTH(123).
!LENGTH(123.00).
!LENGTH( 123 ).
!LENGTH("123").
!LENGTH(xyzzy).
!LENGTH("xyzzy").
!LENGTH("xy""zzy").
!LENGTH(!UNQUOTE("xyzzy")).
!LENGTH(!UNQUOTE("xy""zzy")).
!LENGTH(!NULL).
!ENDDEFINE.
DEFINE !la(!positional !enclose('(',')'))
!LENGTH(!1).
!ENDDEFINE.
DEBUG EXPAND.
!l.
!la(a b c).
!la().
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
3.
6.
3.
5.
5.
7.
9.
5.
6.
0.

5.

0.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !NULL in the manual.
AT_SETUP([macro expansion - !NULL])
AT_KEYWORDS([NULL])
AT_DATA([define.sps], [dnl
DEFINE !n()
!NULL.
!QUOTE(!NULL).
!ENDDEFINE.
DEBUG EXPAND.
!n.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
.
''.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !QUOTE and !UNQUOTE in the manual.
AT_SETUP([macro expansion - !QUOTE and !UNQUOTE])
AT_KEYWORDS([QUOTE UNQUOTE])
AT_DATA([define.sps], [dnl
DEFINE !q(!POS !CMDEND)
!QUOTE(123.0).
!QUOTE( 123 ).
!QUOTE('a b c').
!QUOTE("a b c").
!QUOTE(!1).

!UNQUOTE(123.0).
!UNQUOTE( 123 ).
!UNQUOTE('a b c').
!UNQUOTE("a b c").
!UNQUOTE(!1).

!QUOTE(!UNQUOTE(123.0)).
!QUOTE(!UNQUOTE( 123 )).
!QUOTE(!UNQUOTE('a b c')).
!QUOTE(!UNQUOTE("a b c")).
!QUOTE(!UNQUOTE(!1)).
!ENDDEFINE.
DEBUG EXPAND.
!q a 'b' c.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
'123.0'.
'123'.
'a b c'.
"a b c".
'a ''b'' c'.

123.0.
123.
a b c.
a b c.
a 'b' c.

'123.0'.
'123'.
'a b c'.
'a b c'.
'a ''b'' c'.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !SUBSTR in the manual.
AT_SETUP([macro expansion - !SUBSTR])
AT_KEYWORDS([SUBSTR])
AT_DATA([define.sps], [dnl
DEFINE !s()
!SUBSTR(banana, 3).
!SUBSTR(banana, 3, 3).
!SUBSTR("banana", 1, 3).
!SUBSTR(!UNQUOTE("banana"), 3).
!SUBSTR("banana", 3, 3).
!SUBSTR(banana, 3, 0).
!SUBSTR(banana, 3, 10).
!SUBSTR(banana, 10, 3).
!ENDDEFINE.
DEBUG EXPAND.
!s.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1-10: At `"ba' in the expansion of `!s',dnl "

define.sps:12.1-12.2: error: DEBUG EXPAND: Unterminated string constant.
   12 | !s.
      | ^~

nana.
nan.
.
nana.
ana.
.
nana.
.
])
AT_CLEANUP

dnl Keep this test in sync with the examples for !UPCASE in the manual.
AT_SETUP([macro expansion - !UPCASE])
AT_KEYWORDS([UPCASE])
AT_DATA([define.sps], [dnl
DEFINE !u()
!UPCASE(freckle).
!UPCASE('freckle').
!UPCASE('a b c').
!UPCASE('A B C').
!ENDDEFINE.
DEBUG EXPAND.
!u.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
FRECKLE.
FRECKLE.
A B C.
A B C.
])
AT_CLEANUP

dnl !* is implemented separately inside and outside function arguments
dnl so this test makes sure to include both.
AT_SETUP([macro expansion - !*])
AT_DATA([define.sps], [dnl
DEFINE !m(!POSITIONAL !TOKENS(1)
         /!POSITIONAL !TOKENS(1))
!*/
!LENGTH(!*)/
!SUBSTR(!*, 3)/
!QUOTE(!*).
!ENDDEFINE.
DEBUG EXPAND.
!m 123 b
!m 2 3
!m '' 'b'.
])
AT_CAPTURE_FILE([define.sps])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
123 b / 5 / 3 b / '123 b'.

2 3 / 3 / 3 / '2 3'.

'' 'b' / 6 / 'b' / ''''' ''b'''.
])
AT_CLEANUP

AT_SETUP([macro maximum nesting level (MNEST)])
AT_KEYWORDS([MNEST])
AT_DATA([define.sps], [dnl
DEFINE !macro()
!macro
!ENDDEFINE.
!macro.
])
AT_CHECK([pspp -O format=csv define.sps], [1], [dnl
"define.sps:1-3: In the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:1-3: inside the expansion of `!macro',
define.sps:4.1-4.6: error: Maximum nesting level 50 exceeded.  (Use SET MNEST to change the limit.)
    4 | !macro.
      | ^~~~~~"

"define.sps:4.1-4.6: error: In syntax expanded from `!macro': Syntax error expecting command name.
    4 | !macro.
      | ^~~~~~"
])
AT_CLEANUP

AT_SETUP([macro !IF condition])
AT_KEYWORDS([if])
for operators in \
    '!eq !ne !lt !gt !le !ge' \
    '  =  <>   <   >  <=  >='
do
    set $operators
    AS_BOX([$operators])
    cat > define.sps <<EOF
DEFINE !test(!positional !tokens(1))
!if (!1 $1 1) !then true !else false !ifend
!if (!1 $2 1) !then true !else false !ifend
!if (!1 $3 1) !then true !else false !ifend
!if (!1 $4 1) !then true !else false !ifend
!if (!1 $5 1) !then true !else false !ifend
!if (!1 $6 1) !then true !else false !ifend.
!ENDDEFINE.
DEBUG EXPAND.
!test 0
!test 1
!test 2
!test '1'
!test 1.0
EOF
    AT_CAPTURE_FILE([define.sps])
    AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
false true true false true false.

true false false false true true.

false true false true false true.

true false false false true true.

false true false true false true.
])
done
AT_CLEANUP

AT_SETUP([macro !IF condition -- case sensitivity])
AT_KEYWORDS([if])
for operators in \
    '!eq !ne !lt !gt !le !ge' \
    '  =  <>   <   >  <=  >='
do
    set $operators
    AS_BOX([$operators])
    cat > define.sps <<EOF
DEFINE !test(!positional !tokens(1))
!if (!1 $1 a) !then true !else false !ifend
!if (!1 $1 A) !then true !else false !ifend
!if (!1 $2 a) !then true !else false !ifend
!if (!1 $2 A) !then true !else false !ifend
!if (!1 $3 a) !then true !else false !ifend
!if (!1 $3 A) !then true !else false !ifend
!if (!1 $4 a) !then true !else false !ifend
!if (!1 $4 A) !then true !else false !ifend
!if (!1 $5 a) !then true !else false !ifend
!if (!1 $5 A) !then true !else false !ifend
!if (!1 $6 a) !then true !else false !ifend
!if (!1 $6 A) !then true !else false !ifend
!if (!1 $1 !null) !then true !else false !ifend
!if (!1 $2 !null) !then true !else false !ifend.
!ENDDEFINE.
DEBUG EXPAND.
!test a
!test A
!test b
!test B
EOF
    AT_CAPTURE_FILE([define.sps])
    AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
true false false true false false false true true false true true false true.

false true true false true false false false true true false true false true.

false false true true false false true true false false true true false true.

false false true true true false false true true false false true false true.
])
done
AT_CLEANUP

AT_SETUP([macro !IF condition -- logical operators])
AT_KEYWORDS([if])
for operators in \
    '!and !or !not' \
    '   &   |    ~'
do
    set $operators
    AS_BOX([$operators])
    cat > define.sps <<EOF
DEFINE !test_binary(!positional !tokens(1)/!positional !tokens(1))
!if !1 $1 !2 !then true !else false !ifend
!if !1 $2 !2 !then true !else false !ifend.
!ENDDEFINE.

DEFINE !test_unary(!positional !tokens(1))
!if $3 !1 !then true !else false !ifend.
!ENDDEFINE.

* These are:
  ((not A) and B) or C
  not (A and B) or C
  not A and (B or C)
DEFINE !test_prec(!pos !tokens(1)/!pos !tokens(1)/!pos !tokens(1))
!if $3 !1 $1 !2 $2 !3 !then true !else false !ifend
!if $3 (!1 $1 !2) $2 !3 !then true !else false !ifend
!if $3 !1 $1 (!2 $2 !3) !then true !else false !ifend
!ENDDEFINE.

DEBUG EXPAND.
!test_binary 0 0
!test_binary 0 1
!test_binary 1 0
!test_binary 1 1
!test_unary 0
!test_unary 1
!test_prec 0 0 0 !test_prec 0 0 1 !test_prec 0 1 0 !test_prec 0 1 1.
!test_prec 1 0 0 !test_prec 1 0 1 !test_prec 1 1 0 !test_prec 1 1 1.
EOF
    AT_CAPTURE_FILE([define.sps])
    AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
false false.

false true.

false true.

true true.

true.

false.

false true false
true true true
true true true
true true true

false true false
true true false
false false false
true true false
])
done
AT_CLEANUP

AT_SETUP([macro !LET])
AT_KEYWORDS([let])
AT_DATA([define.sps], [dnl
DEFINE !macro(!POS !CMDEND)
!LET !v1 = !CONCAT('x',!1,'y')
!LET !v2 = !QUOTE(!v1)
!LET !v3 = (!LENGTH(!1) = 1)
!LET !v4 = (!SUBSTR(!1, 3) = !NULL)
v1=!v1.
v2=!v2.
v3=!v3.
v4=!v4.
!ENDDEFINE.
DEBUG EXPAND.
!macro 0.
!macro.
!macro xyzzy.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
v1 = x0y.
v2 = x0y.
v3 = 1.
v4 = 1.

v1 = xy.
v2 = xy.
v3 = 0.
v4 = 1.

v1 = xxyzzyy.
v2 = xxyzzyy.
v3 = 0.
v4 = 0.
])
AT_CLEANUP

AT_SETUP([macro indexed !DO])
AT_KEYWORDS([index do])
AT_DATA([define.sps], [dnl
DEFINE !title(!POS !TOKENS(1)) !1. !ENDDEFINE.

DEFINE !for(!POS !TOKENS(1) / !POS !TOKENS(1))
!DO !var = !1 !TO !2 !var !DOEND.
!ENDDEFINE.

DEFINE !forby(!POS !TOKENS(1) / !POS !TOKENS(1) / !POS !TOKENS(1))
!DO !var = !1 !TO !2 !BY !3 !var !DOEND.
!ENDDEFINE.

DEBUG EXPAND.
!title "increasing".
!for 1 5.
!forby 1 5 1.
!forby 1 5 2.
!forby 1 5 2.5.
!forby 1 5 -1.

!title "decreasing".
!for 5 1.
!forby 5 1 1.
!forby 5 1 -1.
!forby 5 1 -2.
!forby 5 1 -3.

!title "non-integer".
!for 1.5 3.5.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
"increasing".

1 2 3 4 5.

1 2 3 4 5.

1 3 5.

1 3.5.

.

"decreasing".

.

.

5 4 3 2 1.

5 3 1.

5 2.

"non-integer".

1.5 2.5 3.5.
])
AT_CLEANUP

AT_SETUP([macro !DO invalid variable names])
AT_KEYWORDS([index do])
AT_DATA([define.sps], [dnl
DEFINE !for(x=!TOKENS(1) / y=!TOKENS(1))
!DO !x = !x !TO !y !var !DOEND.
!ENDDEFINE.

DEFINE !for2(x=!TOKENS(1) / y=!TOKENS(1))
!DO !noexpand = !x !TO !y !var !DOEND.
!ENDDEFINE.

DEBUG EXPAND.
!for x=1 y=5.
!for2 x=1 y=5.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1-3: At `!x' in the expansion of `!for',
define.sps:10.1-10.12: error: DEBUG EXPAND: Cannot use argument name or macro
keyword as !DO variable.
   10 | !for x=1 y=5.
      | ^~~~~~~~~~~~

!DO 1 = 1 !TO 5 !var !DOEND.

define.sps:5-7: At `!noexpand' in the expansion of `!for2',
define.sps:11.1-11.13: error: DEBUG EXPAND: Cannot use argument name or macro
keyword as !DO variable.
   11 | !for2 x=1 y=5.
      | ^~~~~~~~~~~~~

!DO !noexpand = 1 !TO 5 !var !DOEND.
])
AT_CLEANUP

AT_SETUP([macro indexed !DO reaches MITERATE])
AT_KEYWORDS([index do])
AT_DATA([define.sps], [dnl
DEFINE !title(!POS !TOKENS(1)) !1. !ENDDEFINE.

DEFINE !for(!POS !TOKENS(1) / !POS !TOKENS(1))
!DO !var = !1 !TO !2 !var !DOEND.
!ENDDEFINE.

DEFINE !forby(!POS !TOKENS(1) / !POS !TOKENS(1) / !POS !TOKENS(1))
!DO !var = !1 !TO !2 !BY !3 !var !DOEND.
!ENDDEFINE.

SET MITERATE=3.
DEBUG EXPAND.
!title "increasing".
!for 1 5.
!forby 1 5 1.
!forby 1 5 2.
!forby 1 5 2.5.
!forby 1 5 -1.

!title "decreasing".
!for 5 1.
!forby 5 1 1.
!forby 5 1 -1.
!forby 5 1 -2.
!forby 5 1 -3.

!title "non-integer".
!for 1.5 3.5.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
"increasing".

In the expansion of `!DO',
define.sps:3-5: inside the expansion of `!for',
define.sps:14.1-14.8: error: DEBUG EXPAND: Numerical !DO loop exceeded maximum
number of iterations 3.  (Use SET MITERATE to change the limit.)
   14 | !for 1 5.
      | ^~~~~~~~

1 2 3 4.

In the expansion of `!DO',
define.sps:7-9: inside the expansion of `!forby',
define.sps:15.1-15.12: error: DEBUG EXPAND: Numerical !DO loop exceeded maximum
number of iterations 3.  (Use SET MITERATE to change the limit.)
   15 | !forby 1 5 1.
      | ^~~~~~~~~~~~

1 2 3 4.

1 3 5.

1 3.5.

.

"decreasing".

.

.

In the expansion of `!DO',
define.sps:7-9: inside the expansion of `!forby',
define.sps:23.1-23.13: error: DEBUG EXPAND: Numerical !DO loop exceeded maximum
number of iterations 3.  (Use SET MITERATE to change the limit.)
   23 | !forby 5 1 -1.
      | ^~~~~~~~~~~~~

5 4 3 2.

5 3 1.

5 2.

"non-integer".

1.5 2.5 3.5.
])
AT_CLEANUP

AT_SETUP([!BREAK with macro indexed !DO])
AT_KEYWORDS([index do break])
AT_DATA([define.sps], [dnl
DEFINE !title(!POS !TOKENS(1)) !1. !ENDDEFINE.

DEFINE !for(!POS !TOKENS(1) / !POS !TOKENS(1) / !POS !TOKENS(1))
!DO !var = !1 !TO !2
  !var
  !IF 1 !THEN
    !IF !var = !3 !THEN
      x
      !BREAK
      y
    !IFEND
    ,
  !IFEND
!DOEND.
!ENDDEFINE.

DEBUG EXPAND.
!for 1 5 4.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
1, 2, 3, 4 x.
])
AT_CLEANUP

AT_SETUP([macro list !DO])
AT_KEYWORDS([index do])
AT_DATA([define.sps], [dnl
DEFINE !for(!POS !CMDEND)
(!DO !i !IN (!1) (!i) !DOEND).
!ENDDEFINE.

DEBUG EXPAND.
!for a b c.
!for 'foo bar baz quux'.
!for.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
( (a) (b) (c) ).

( (foo) (bar) (baz) (quux) ).

( ).
])
AT_CLEANUP

AT_SETUP([macro list !DO reaches MITERATE])
AT_KEYWORDS([index do])
AT_DATA([define.sps], [dnl
DEFINE !for(!POS !CMDEND)
(!DO !i !IN (!1) (!i) !DOEND).
!ENDDEFINE.

SET MITERATE=2.
DEBUG EXPAND.
!for a b c.
!for 'foo bar baz quux'.
!for.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
In the expansion of `!DO',
define.sps:1-3: inside the expansion of `!for',
define.sps:7.1-7.10: error: DEBUG EXPAND: !DO loop over list exceeded maximum
number of iterations 2.  (Use SET MITERATE to change the limit.)
    7 | !for a b c.
      | ^~~~~~~~~~

( (a) (b) ).

In the expansion of `!DO',
define.sps:1-3: inside the expansion of `!for',
define.sps:8.1-8.23: error: DEBUG EXPAND: !DO loop over list exceeded maximum
number of iterations 2.  (Use SET MITERATE to change the limit.)
    8 | !for 'foo bar baz quux'.
      | ^~~~~~~~~~~~~~~~~~~~~~~

( (foo) (bar) ).

( ).
])
AT_CLEANUP

AT_SETUP([!BREAK with macro list !DO])
AT_KEYWORDS([index break do])
AT_DATA([define.sps], [dnl
DEFINE !for(!POS !TOKENS(1) / !POS !CMDEND)
(!DO !i !IN (!2)
  (!i)
  !IF 1 !THEN
    !IF !i = !1 !THEN
      x
      !BREAK
      y
    !IFEND
    ,
  !IFEND
!DOEND).
!ENDDEFINE.

DEBUG EXPAND.
!for d a b c.
!for baz 'foo bar baz quux'.
!for e.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
( (a), (b), (c), ).

( (foo), (bar), (baz)x).

( ).
])
AT_CLEANUP

AT_SETUP([macro !LET])
AT_DATA([define.sps], [dnl
DEFINE !macro(!pos !enclose('(',')'))
!LET !x=!1
!LET !y=!QUOTE(!1)
!LET !z=(!y="abc")
!y !z
!ENDDEFINE.

DEBUG EXPAND.
!macro(1+2).
!macro(abc).
])
AT_CHECK([pspp --testing-mode define.sps -O format=csv], [0], [dnl
1 + 2 0

abc 1
])
AT_CLEANUP

AT_SETUP([macro !LET invalid variable names])
AT_DATA([define.sps], [dnl
DEFINE !macro(x=!tokens(1))
!LET !x=!x
!ENDDEFINE.

DEFINE !macro2()
!LET !do=x
!ENDDEFINE.

DEBUG EXPAND.
!macro x=1.
!macro2.
])
AT_CHECK([pspp --testing-mode define.sps -O format=csv], [1], [dnl
"define.sps:1-3: At `!x' in the expansion of `!macro',
define.sps:10.1-10.10: error: DEBUG EXPAND: Cannot use argument name or macro keyword ""!x"" as !LET variable.
   10 | !macro x=1.
      | ^~~~~~~~~~"

!LET 1 = 1

"define.sps:5-7: At `!do' in the expansion of `!macro2',
define.sps:11.1-11.7: error: DEBUG EXPAND: Cannot use argument name or macro keyword ""!do"" as !LET variable.
   11 | !macro2.
      | ^~~~~~~"

"define.sps:5-7: At `=' in the expansion of `!macro2',
define.sps:11.1-11.7: error: DEBUG EXPAND: Expected macro variable name following !DO.
   11 | !macro2.
      | ^~~~~~~"

!LET !do = x
])
AT_CLEANUP

AT_SETUP([BEGIN DATA inside a macro])
AT_DATA([define.sps], [dnl
DEFINE !macro()
DATA LIST NOTABLE /x 1.
BEGIN DATA
1
2
3
END DATA.
LIST.
!ENDDEFINE.

!macro
])
AT_CHECK([pspp define.sps -O format=csv], [0], [dnl
Table: Data List
x
1
2
3
])
AT_CLEANUP

AT_SETUP([TITLE and SUBTITLE with macros])
AT_KEYWORDS([macro])
for command in TITLE SUBTITLE; do
    cat >title.sps <<EOF
DEFINE !paste(!POS !TOKENS(1) / !POS !TOKENS(1))
!CONCAT(!1,!2)
!ENDDEFINE.
$command prefix !paste foo bar suffix.
SHOW $command.
EOF
    cat >expout <<EOF
Table: Settings
$command,prefix foobar suffix
EOF
    AT_CHECK([pspp -O format=csv title.sps], [0], [expout])
done
AT_CLEANUP

AT_SETUP([error message within macro expansion])
AT_DATA([define.sps], [dnl
DEFINE !vars(!POS !TOKENS(1)) a b C !ENDDEFINE.
DATA LIST NOTABLE /a b 1-2.
COMPUTE x = !vars x.
])
AT_CHECK([pspp -O format=csv define.sps], [1], [dnl
"define.sps:3.13-3.19: error: COMPUTE: In syntax expanded from `!vars x': Syntax error expecting end of command.
    3 | COMPUTE x = !vars x.
      |             ^~~~~~~"
])
AT_CLEANUP

dnl A macro with keyword arguments needs a token of lookahead
dnl to find out whether another keyword is present.  Test that
dnl this special case works OK.
AT_SETUP([macro calls in each others' lookahead])
AT_DATA([define.sps], [dnl
DEFINE !k(x=!DEFAULT(0) !TOKENS(1)/y=!DEFAULT(0) !TOKENS(1))
(x=!x)(y=!y)
!ENDDEFINE.
DEBUG EXPAND.
!k
!k x=1
!k y=2
!k y=2 x=1
!k x=1 y=2.
])
AT_CHECK([pspp -O format=csv define.sps --testing-mode], [0], [dnl
(x = 0) (y = 0)

(x = 1) (y = 0)

(x = 0) (y = 2)
(x = 1) (y = 2)

(x = 1) (y = 2)
])
AT_CLEANUP

AT_SETUP([bad token in macro body])
AT_DATA([define.sps], [dnl
DEFINE !x()
x'123'
!ENDDEFINE.
])
AT_CHECK([pspp define.sps], [1], [dnl
define.sps:2.1-2.6: error: DEFINE: String of hex digits has 3 characters, which
is not a multiple of 2.
    2 | x'123'
      | ^~~~~~
])
AT_CLEANUP

AT_SETUP([macro name overlaps with macro function name])
dnl !len is short for macro function !LENGTH.  PSPP used to
dnl reject the following with "`(' expected following !LENGTH".
dnl Now PSPP only consider macro functions when the name is
dnl followed by '('.
AT_DATA([define.sps], [dnl
DEFINE !len() 5 !ENDDEFINE.
DEFINE !x() !eval(!len) !ENDDEFINE.
DEBUG EXPAND.
!x
])
AT_CHECK([pspp -O format=csv define.sps --testing-mode], [0], [dnl
5
])
AT_CLEANUP

AT_SETUP([generic macro function syntax errors])
AT_DATA([define.sps], [dnl


DEFINE !c() !SUBSTR(1x) !ENDDEFINE.
DEFINE !d() !SUBSTR(1 !ENDDEFINE.
DEFINE !narg_blanks() !BLANKS() !ENDDEFINE.
DEFINE !narg_concat() !CONCAT() !ENDDEFINE.
DEFINE !narg_eval() !EVAL() !ENDDEFINE.
DEFINE !narg_head() !HEAD() !ENDDEFINE.
DEFINE !narg_index() !INDEX() !ENDDEFINE.
DEFINE !narg_length() !LENGTH() !ENDDEFINE.
DEFINE !narg_null() !NULL() !ENDDEFINE.
DEFINE !narg_quote() !QUOTE() !ENDDEFINE.
DEFINE !narg_substr() !SUBSTR() !ENDDEFINE.
DEFINE !narg_tail() !TAIL() !ENDDEFINE.
DEFINE !narg_unquote() !UNQUOTE() !ENDDEFINE.
DEFINE !narg_upcase() !UPCASE() !ENDDEFINE.
dnl )
DEBUG EXPAND.


!c.
!d.
!narg_blanks.
!narg_concat.
!narg_eval.
!narg_head.
!narg_index.
!narg_length.
!narg_null.
!narg_quote.
!narg_substr.
!narg_tail.
!narg_unquote.
!narg_upcase.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:3: At `x' in the expansion of `!c',
define.sps:20.1-20.2: error: DEBUG EXPAND: `,' or `@:}@' expected in call to macro
function !SUBSTR.
   20 | !c.
      | ^~

!SUBSTR(1 x)

define.sps:4: In the expansion of `!d',
define.sps:21.1-21.2: error: DEBUG EXPAND: Missing `@:}@' in call to macro
function !SUBSTR.
   21 | !d.
      | ^~

!SUBSTR@{:@1

define.sps:5: In the expansion of `!narg_blanks',
define.sps:22.1-22.12: error: DEBUG EXPAND: Macro function !BLANKS takes one
argument (not 0).
   22 | !narg_blanks.
      | ^~~~~~~~~~~~

!BLANKS( )

define.sps:6: In the expansion of `!narg_concat',
define.sps:23.1-23.12: error: DEBUG EXPAND: Macro function !CONCAT needs at
least one argument.
   23 | !narg_concat.
      | ^~~~~~~~~~~~

!CONCAT( )

define.sps:7: In the expansion of `!narg_eval',
define.sps:24.1-24.10: error: DEBUG EXPAND: Macro function !EVAL takes one
argument (not 0).
   24 | !narg_eval.
      | ^~~~~~~~~~

!EVAL( )

define.sps:8: In the expansion of `!narg_head',
define.sps:25.1-25.10: error: DEBUG EXPAND: Macro function !HEAD takes one
argument (not 0).
   25 | !narg_head.
      | ^~~~~~~~~~

!HEAD( )

define.sps:9: In the expansion of `!narg_index',
define.sps:26.1-26.11: error: DEBUG EXPAND: Macro function !INDEX takes two
arguments (not 0).
   26 | !narg_index.
      | ^~~~~~~~~~~

!INDEX( )

define.sps:10: In the expansion of `!narg_length',
define.sps:27.1-27.12: error: DEBUG EXPAND: Macro function !LENGTH takes one
argument (not 0).
   27 | !narg_length.
      | ^~~~~~~~~~~~

!LENGTH( )

( )

define.sps:12: In the expansion of `!narg_quote',
define.sps:29.1-29.11: error: DEBUG EXPAND: Macro function !QUOTE takes one
argument (not 0).
   29 | !narg_quote.
      | ^~~~~~~~~~~

!QUOTE( )

define.sps:13: In the expansion of `!narg_substr',
define.sps:30.1-30.12: error: DEBUG EXPAND: Macro function !SUBSTR takes two or
three arguments (not 0).
   30 | !narg_substr.
      | ^~~~~~~~~~~~

!SUBSTR( )

define.sps:14: In the expansion of `!narg_tail',
define.sps:31.1-31.10: error: DEBUG EXPAND: Macro function !TAIL takes one
argument (not 0).
   31 | !narg_tail.
      | ^~~~~~~~~~

!TAIL( )

define.sps:15: In the expansion of `!narg_unquote',
define.sps:32.1-32.13: error: DEBUG EXPAND: Macro function !UNQUOTE takes one
argument (not 0).
   32 | !narg_unquote.
      | ^~~~~~~~~~~~~

!UNQUOTE( )

define.sps:16: In the expansion of `!narg_upcase',
define.sps:33.1-33.12: error: DEBUG EXPAND: Macro function !UPCASE takes one
argument (not 0).
   33 | !narg_upcase.
      | ^~~~~~~~~~~~

!UPCASE( )
])
AT_CLEANUP

AT_SETUP([specific macro function syntax errors])
AT_DATA([define.sps], [dnl
DEFINE !a() !BLANKS(x). !ENDDEFINE.
DEFINE !b() !SUBSTR(x, y). !ENDDEFINE.
DEFINE !c() !SUBSTR(x, 1, z). !ENDDEFINE.
DEBUG EXPAND.
!a.
!b.
!c.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1: In the expansion of `!a',
define.sps:5.1-5.2: error: DEBUG EXPAND: Argument to !BLANKS must be non-
negative integer (not "x").
    5 | !a.
      | ^~

!BLANKS(x).

define.sps:2: In the expansion of `!b',
define.sps:6.1-6.2: error: DEBUG EXPAND: Second argument of !SUBSTR must be
positive integer (not "y").
    6 | !b.
      | ^~

!SUBSTR(x, y).

define.sps:3: In the expansion of `!c',
define.sps:7.1-7.2: error: DEBUG EXPAND: Third argument of !SUBSTR must be non-
negative integer (not "z").
    7 | !c.
      | ^~

!SUBSTR(x, 1, z).
])
AT_CLEANUP

AT_SETUP([macro expression errors])
AT_DATA([define.sps], [dnl
DEFINE !a() !LET !x = (1. !ENDDEFINE dnl )

DEFINE !b() !DO !x = x. !ENDDEFINE.
DEFINE !c() !LET !x = (). !ENDDEFINE.
DEBUG EXPAND.
!a.
!b.
!c.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1-2: At `.' in the expansion of `!a',
define.sps:5.1-5.2: error: DEBUG EXPAND: Expecting ')' in macro expression.
    5 | !a.
      | ^~

!LET !x = (1.

At `x' in the expansion of `!DO',
define.sps:2: inside the expansion of `!b',
define.sps:6.1-6.2: error: DEBUG EXPAND: Macro expression must evaluate to a
number (not "x").
    6 | !b.
      | ^~

!DO !x = x.

define.sps:3: At `)' in the expansion of `!c',
define.sps:7.1-7.2: error: DEBUG EXPAND: Expecting literal or function
invocation in macro expression.
    7 | !c.
      | ^~

!LET !x = ( ).
])
AT_CLEANUP

AT_SETUP([macro !IF errors])
AT_KEYWORDS([IF])
AT_DATA([define.sps], [dnl
DEFINE !a() !IF 1 !ENDDEFINE.
DEFINE !b() !IF 1 !THEN !ENDDEFINE.
DEFINE !c() !IF 1 !THEN !ELSE !ENDDEFINE.
DEBUG EXPAND.
!a.
!b.
!c.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1: In the expansion of `!a',
define.sps:5.1-5.2: error: DEBUG EXPAND: !THEN expected in macro !IF construct.
    5 | !a.
      | ^~

!IF 1

define.sps:2: In the expansion of `!b',
define.sps:6.1-6.2: error: DEBUG EXPAND: !ELSE or !IFEND expected in macro !IF
construct.
    6 | !b.
      | ^~

!IF 1 !THEN

define.sps:3: In the expansion of `!c',
define.sps:7.1-7.2: error: DEBUG EXPAND: !IFEND expected in macro !IF
construct.
    7 | !c.
      | ^~

!IF 1 !THEN !ELSE
])
AT_CLEANUP

AT_SETUP([macro !LET errors])
AT_KEYWORDS([LET])
AT_DATA([define.sps], [dnl
DEFINE !a() !LET !ENDDEFINE.
DEFINE !b() !LET 0 !ENDDEFINE.
DEFINE !c() !LET !x !ENDDEFINE.
DEFINE !d() !LET !x y !ENDDEFINE.
DEBUG EXPAND.
!a.
!b.
!c.
!d.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1: In the expansion of `!a',
define.sps:6.1-6.2: error: DEBUG EXPAND: Expected macro variable name following
!LET.
    6 | !a.
      | ^~

!LET

define.sps:2: At `0' in the expansion of `!b',
define.sps:7.1-7.2: error: DEBUG EXPAND: Expected macro variable name following
!LET.
    7 | !b.
      | ^~

!LET 0

define.sps:3: In the expansion of `!c',
define.sps:8.1-8.2: error: DEBUG EXPAND: Expected `=' following !LET.
    8 | !c.
      | ^~

!LET !x

define.sps:4: At `y' in the expansion of `!d',
define.sps:9.1-9.2: error: DEBUG EXPAND: Expected `=' following !LET.
    9 | !d.
      | ^~

!LET !x y
])
AT_CLEANUP

AT_SETUP([macro !DO errors])
AT_KEYWORDS([DO])
AT_DATA([define.sps], [dnl
DEFINE !a() !DO !ENDDEFINE.
DEFINE !b() !DO 0 !ENDDEFINE.
DEFINE !c() !DO !x !ENDDEFINE.
DEFINE !d() !DO !x !in (x) !ENDDEFINE.
DEFINE !e() !DO !x = x. !ENDDEFINE.
DEFINE !f() !DO !x = 5 x !ENDDEFINE.
DEFINE !g() !DO !x = 5 !TO 6 !BY 0 !ENDDEFINE.
DEFINE !h() !DO !x !ENDDEFINE.
DEFINE !i() !DO !x 0 !ENDDEFINE.
DEFINE !j() !BREAK !ENDDEFINE.
DEBUG EXPAND.
!a.
!b.
!c.
!d.
!e.
!f.
!g.
!h.
!i.
!j.
])
AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
define.sps:1: In the expansion of `!a',
define.sps:12.1-12.2: error: DEBUG EXPAND: Expected macro variable name
following !DO.
   12 | !a.
      | ^~

!DO

define.sps:2: At `0' in the expansion of `!b',
define.sps:13.1-13.2: error: DEBUG EXPAND: Expected macro variable name
following !DO.
   13 | !b.
      | ^~

!DO 0

define.sps:3: In the expansion of `!c',
define.sps:14.1-14.2: error: DEBUG EXPAND: Expected `=' or !IN in !DO loop.
   14 | !c.
      | ^~

!DO !x

In the expansion of `!DO',
define.sps:4: inside the expansion of `!d',
define.sps:15.1-15.2: error: DEBUG EXPAND: Missing !DOEND.
   15 | !d.
      | ^~

!DO !x !in(x)

At `x' in the expansion of `!DO',
define.sps:5: inside the expansion of `!e',
define.sps:16.1-16.2: error: DEBUG EXPAND: Macro expression must evaluate to a
number (not "x").
   16 | !e.
      | ^~

!DO !x = x.

At `x' in the expansion of `!DO',
define.sps:6: inside the expansion of `!f',
define.sps:17.1-17.2: error: DEBUG EXPAND: Expected !TO in numerical !DO loop.
   17 | !f.
      | ^~

!DO !x = 5 x

In the expansion of `!DO',
define.sps:7: inside the expansion of `!g',
define.sps:18.1-18.2: error: DEBUG EXPAND: !BY value cannot be zero.
   18 | !g.
      | ^~

!DO !x = 5 !TO 6 !BY 0

define.sps:8: In the expansion of `!h',
define.sps:19.1-19.2: error: DEBUG EXPAND: Expected `=' or !IN in !DO loop.
   19 | !h.
      | ^~

!DO !x

define.sps:9: At `0' in the expansion of `!i',
define.sps:20.1-20.2: error: DEBUG EXPAND: Expected `=' or !IN in !DO loop.
   20 | !i.
      | ^~

!DO !x 0

define.sps:10: At `!BREAK' in the expansion of `!j',
define.sps:21.1-21.2: error: DEBUG EXPAND: !BREAK outside !DO.
   21 | !j.
      | ^~

])
AT_CLEANUP

AT_SETUP([macros in comments])
AT_KEYWORDS([macro])
AT_DATA([define.sps], [dnl
DEFINE !macro() x y z !ENDDEFINE.
/* !macro.
*!macro.
DEBUG EXPAND.
!macro.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
x y z
])
AT_CLEANUP

AT_SETUP([DEFINE syntax errors])
AT_KEYWORDS([macro])
AT_DATA([define.sps], [dnl
DEFINE !macro(!POSITIONAL !CHAREND('x y')) !ENDDEFINE.
DEFINE !macro(a=!TOKENS(1)/!POSITIONAL !TOKENS(1)) !ENDDEFINE.
DEFINE !macro(!a=!TOKENS(1)) !ENDDEFINE.
DEFINE !macro(do=!TOKENS(1)) !ENDDEFINE.
DEFINE 0() !ENDDEFINE.
DEFINE x y () !ENDDEFINE.
DEFINE !macro(1) !ENDDEFINE.
DEFINE !macro(x 2) !ENDDEFINE.
DEFINE !macro(x=!DEFAULT 3) !ENDDEFINE.
DEFINE !macro(x=!TOKENS 4) !ENDDEFINE.
DEFINE !macro(x=!TOKENS(x)) !ENDDEFINE.
DEFINE !macro(x=!TOKENS(1 5)) !ENDDEFINE.
DEFINE !macro(x=!ENCLOSE 6) !ENDDEFINE.
DEFINE !macro(x=!ENCLOSE('x' y)) !ENDDEFINE.
DEFINE !macro(x=!ENCLOSE('x',y)) !ENDDEFINE.
DEFINE !macro(x=!ENCLOSE('x','y' z)) !ENDDEFINE.
DEFINE !macro(x=!CHAREND 7) !ENDDEFINE.
DEFINE !macro(x=!CHAREND(8)) !ENDDEFINE.
DEFINE !macro(x=!CHAREND('x' 9)) !ENDDEFINE.
DEFINE !macro(x=!WTF) !ENDDEFINE.
DEFINE !macro(x=!TOKENS(1) x) !ENDDEFINE.
DEFINE !macro(x=!DEFAULT() !DEFAULT()) !ENDDEFINE.
DEFINE !macro(x=!TOKENS(1) !CMDEND) !ENDDEFINE.
DEFINE !macro()
])
AT_CHECK([pspp define.sps], [1], [dnl
define.sps:1.36-1.40: error: DEFINE: String must contain exactly one token.
    1 | DEFINE !macro(!POSITIONAL !CHAREND('x y')) !ENDDEFINE.
      |                                    ^~~~~

define.sps:2.28-2.38: error: DEFINE: Positional parameters must precede keyword
parameters.
    2 | DEFINE !macro(a=!TOKENS(1)/!POSITIONAL !TOKENS(1)) !ENDDEFINE.
      |                            ^~~~~~~~~~~

define.sps:2.15: note: DEFINE: Here is a previous keyword parameter.
    2 | DEFINE !macro(a=!TOKENS(1)/!POSITIONAL !TOKENS(1)) !ENDDEFINE.
      |               ^

define.sps:3.15-3.16: error: DEFINE: Keyword macro parameter must be named in
definition without "!" prefix.
    3 | DEFINE !macro(!a=!TOKENS(1)) !ENDDEFINE.
      |               ^~

define.sps:4.15-4.16: error: DEFINE: Cannot use macro keyword "do" as an
argument name.
    4 | DEFINE !macro(do=!TOKENS(1)) !ENDDEFINE.
      |               ^~

define.sps:5.8: error: DEFINE: Syntax error expecting identifier.
    5 | DEFINE 0() !ENDDEFINE.
      |        ^

define.sps:6.10: error: DEFINE: Syntax error expecting `@{:@'.
    6 | DEFINE x y () !ENDDEFINE.
      |          ^

define.sps:7.15: error: DEFINE: Syntax error expecting identifier.
    7 | DEFINE !macro(1) !ENDDEFINE.
      |               ^

define.sps:8.17: error: DEFINE: Syntax error expecting !TOKENS, !CHAREND, !
ENCLOSE, or !CMDEND.
    8 | DEFINE !macro(x 2) !ENDDEFINE.
      |                 ^

define.sps:9.26: error: DEFINE: Syntax error expecting `@{:@'.
    9 | DEFINE !macro(x=!DEFAULT 3) !ENDDEFINE.
      |                          ^

define.sps:10.25: error: DEFINE: Syntax error expecting `@{:@'.
   10 | DEFINE !macro(x=!TOKENS 4) !ENDDEFINE.
      |                         ^

define.sps:11.25: error: DEFINE: Syntax error expecting positive integer for !
TOKENS.
   11 | DEFINE !macro(x=!TOKENS(x)) !ENDDEFINE.
      |                         ^

define.sps:12.27: error: DEFINE: Syntax error expecting `@:}@'.
   12 | DEFINE !macro(x=!TOKENS(1 5)) !ENDDEFINE.
      |                           ^

define.sps:13.26: error: DEFINE: Syntax error expecting `@{:@'.
   13 | DEFINE !macro(x=!ENCLOSE 6) !ENDDEFINE.
      |                          ^

define.sps:14.30: error: DEFINE: Syntax error expecting `,'.
   14 | DEFINE !macro(x=!ENCLOSE('x' y)) !ENDDEFINE.
      |                              ^

define.sps:15.30: error: DEFINE: Syntax error expecting string.
   15 | DEFINE !macro(x=!ENCLOSE('x',y)) !ENDDEFINE.
      |                              ^

define.sps:16.34: error: DEFINE: Syntax error expecting `@:}@'.
   16 | DEFINE !macro(x=!ENCLOSE('x','y' z)) !ENDDEFINE.
      |                                  ^

define.sps:17.26: error: DEFINE: Syntax error expecting `@{:@'.
   17 | DEFINE !macro(x=!CHAREND 7) !ENDDEFINE.
      |                          ^

define.sps:18.26: error: DEFINE: Syntax error expecting string.
   18 | DEFINE !macro(x=!CHAREND(8)) !ENDDEFINE.
      |                          ^

define.sps:19.30: error: DEFINE: Syntax error expecting `@:}@'.
   19 | DEFINE !macro(x=!CHAREND('x' 9)) !ENDDEFINE.
      |                              ^

define.sps:20.17-20.20: error: DEFINE: Syntax error expecting !TOKENS, !
CHAREND, !ENCLOSE, or !CMDEND.
   20 | DEFINE !macro(x=!WTF) !ENDDEFINE.
      |                 ^~~~

define.sps:21.28: error: DEFINE: Syntax error expecting `/'.
   21 | DEFINE !macro(x=!TOKENS(1) x) !ENDDEFINE.
      |                            ^

define.sps:22.28-22.35: error: DEFINE: !DEFAULT is allowed only once per
argument.
   22 | DEFINE !macro(x=!DEFAULT() !DEFAULT()) !ENDDEFINE.
      |                            ^~~~~~~~

define.sps:23.28-23.34: error: DEFINE: Only one of !TOKENS, !CHAREND, !ENCLOSE,
or !CMDEND is allowed.
   23 | DEFINE !macro(x=!TOKENS(1) !CMDEND) !ENDDEFINE.
      |                            ^~~~~~~

define.sps:25.1: error: DEFINE: Syntax error expecting macro body or !
ENDDEFINE.
   25 |
      | ^
])
AT_CLEANUP

AT_SETUP([macro expansion with token merging])
AT_DATA([define.sps], [dnl
DEFINE !foo() "foo" !ENDDEFINE.
DEFINE !bar() "bar" !ENDDEFINE.
DEFINE !plus() + !ENDDEFINE.
DEFINE !minus() - !ENDDEFINE.
DEFINE !one() 1 !ENDDEFINE.
ECHO "foo" + "bar".
ECHO !foo.
ECHO !bar.
ECHO !foo + "quux".
ECHO "baz" + !bar.
ECHO !foo + !bar.
ECHO !foo !plus !bar.
ECHO "two" "strings".
N OF CASES -/**/1.
N OF CASES !minus 1.
N OF CASES - !one.
N OF CASES !minus !one.
])
AT_CHECK([pspp define.sps], [1], [dnl
foobar

foo

bar

fooquux

bazbar

foobar

foobar

two

define.sps:13.12-13.20: error: ECHO: Syntax error expecting end of command.
   13 | ECHO "two" "strings".
      |            ^~~~~~~~~

define.sps:14.12-14.17: error: N OF CASES: Syntax error expecting positive
integer for N OF CASES.
   14 | N OF CASES -/**/1.
      |            ^~~~~~

define.sps:15.12-15.19: error: N OF CASES: Syntax error expecting positive
integer for N OF CASES.
   15 | N OF CASES !minus 1.
      |            ^~~~~~~~

define.sps:16.12-16.17: error: N OF CASES: Syntax error expecting positive
integer for N OF CASES.
   16 | N OF CASES - !one.
      |            ^~~~~~

define.sps:17.12-17.22: error: N OF CASES: Syntax error expecting positive
integer for N OF CASES.
   17 | N OF CASES !minus !one.
      |            ^~~~~~~~~~~
])
AT_CLEANUP

AT_SETUP([one macro calls another])
AT_DATA([define.sps], [dnl
DEFINE !a(!pos !enclose('(',')')) [[!1]] !ENDDEFINE.
DEFINE !b(!pos !enclose('{','}')) !a(x !1 z) !ENDDEFINE.
DEFINE !c(!pos !enclose('{','}')) !let !tmp=!quote(!concat('<',!1,'>')) !a(!tmp) !ENDDEFINE.
DEBUG EXPAND.
!b{y}.
!c{y}.
])
AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
[[x y z]]

[[ < y > ]]
])
AT_CLEANUP

AT_SETUP([DISPLAY MACROS])
AT_DATA([define.sps], [dnl
DEFINE !b() 0 !ENDDEFINE.
DEFINE !macro()
a b c d
e f g h.
i j k l
1,2,3,4.
5+6+7.
m(n,o).
"a" "b" "c" 'a' 'b' 'c'.
"x "" y".
!ENDDEFINE.
DEFINE !a() 1 !ENDDEFINE.
DISPLAY MACROS.
])
AT_CHECK([pspp -O format=csv define.sps], [0], [dnl
Table: Macros
Name,Source Location
!a,define.sps:12
!b,define.sps:1
!macro,define.sps:2-11
])
AT_CLEANUP
