diff --git a/changelog.txt b/changelog.txt index 4ae7d8fd6..2bb9185c0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -119,6 +119,7 @@ Version 1.10.0 - gas64: fix structures passed by upcasting (SARG) - fbc: discard assignments and constructor calls in initializers that would write outside of the target when emitting initializer lists - previously, up-casted initializers were writing beyond the limits of the target and trashing memory - fix regression due to previous change on sf.net #917: optimization of 'm += s'. The optimization failed to consider 's = s + expr' where expr might contain 's'. In such a case, the optimization cannot be applied. +- fbc: basic-macros: improve the handling of optional parens in function like macros by ending a macro argument list on the number of arguments expected (non-variadic macros only). Version 1.09.0 diff --git a/src/compiler/pp-define.bas b/src/compiler/pp-define.bas index 287a70ae1..8c08f8b25 100644 --- a/src/compiler/pp-define.bas +++ b/src/compiler/pp-define.bas @@ -66,19 +66,22 @@ private function hLoadMacro _ function = -1 var hasParens = FALSE - - '' TODO: we don't know if this paren is the start of the argument list - '' or is part of the expression for the first argument. var hasWhitespace = lexEatWhitespace( ) '' '('? if( lexCurrentChar( ) = CHAR_LPRNT ) then - hasParens = true - else - if( (pp.invoking > 0) or ((symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) ) then + hasParens = TRUE + end if + + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) then + if( hasParens = FALSE ) then '' not an error, macro can be passed as param to other macros exit function end if + else + '' parens optional? then whitespace before '(' will determine that the '(' + '' is part of the first argument instead of starting the argument list + hasParens and= not hasWhitespace end if if (isMacroAllowed(s) = FALSE) then @@ -141,8 +144,23 @@ private function hLoadMacro _ case CHAR_RPRNT if( prntcnt > 0 ) then prntcnt -= 1 - '' Closing ')'? + + '' determine if we need to add back the right paren ')' + '' if parens were optional and we didn't have any parens + '' for this macro to start with, we need to add back the + '' right paren ')' since it will be needed by the caller. if( prntcnt = 0 ) then + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) = 0 ) then + if( hasParens = FALSE ) then + if( argtb->count >= symbGetDefineParams( s ) ) then + if( pp.invoking > 1 ) then + readdchar = CHAR_RPRNT + end if + end if + end if + end if + + '' Closing ')' exit do end if end if @@ -154,6 +172,27 @@ private function hLoadMacro _ '' "..." vararg, which just "absorbs" everything '' until the closing ')'. if( prntcnt = 1 ) then + + '' Check if comma is ending the macro list of arguments: + '' if parens are optional and we didn't have any parens + '' for this macro to start with, and we now have enough + '' arguments to satisfy the macro, then we can consider + '' the macro call complete. Add back the comma for the + '' caller. + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) = 0 ) then + if( is_variadic = FALSE ) then + if( hasParens = FALSE ) then + if( argtb->count >= symbGetDefineParams( s ) ) then + if( pp.invoking > 1 ) then + readdchar = CHAR_COMMA + end if + prntcnt = 0 + exit do + end if + end if + end if + end if + if( argtb ) then if( argtb->count = 0 ) then argtb->count = 1 @@ -192,6 +231,8 @@ private function hLoadMacro _ end select + '' we are still in an argument, so just join the current + '' token to the current argument if( argtb <> NULL ) then if( t.dtype <> FB_DATATYPE_WCHAR ) then DZstrConcatAssign( argtb->tb(num).text, t.text ) @@ -386,12 +427,18 @@ private function hLoadDefine _ '' '('? if( lexCurrentChar( ) = CHAR_LPRNT ) then - hasParens = true - else - '' not an error, macro can be passed as param to other macros - if( (pp.invoking > 0) or ((symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) ) then + hasParens = TRUE + end if + + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) then + if( hasParens = FALSE ) then + '' not an error, macro can be passed as param to other macros exit function end if + else + '' parens optional? then whitespace before '(' will determine that the '(' + '' is part of the first argument instead of starting the argument list + hasParens and= not hasWhitespace end if if( hasParens ) then @@ -456,19 +503,22 @@ private function hLoadMacroW _ function = -1 var hasParens = FALSE - - '' TODO: we don't know if this paren is the start of the argument list - '' or is part of the expression for the first argument. var hasWhitespace = lexEatWhitespace( ) '' '('? if( lexCurrentChar( ) = CHAR_LPRNT ) then - hasParens = true - else - if( (pp.invoking > 0) or ((symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) ) then + hasParens = TRUE + end if + + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) then + if( hasParens = FALSE ) then '' not an error, macro can be passed as param to other macros exit function end if + else + '' parens optional? then whitespace before '(' will determine that the '(' + '' is part of the first argument instead of starting the argument list + hasParens and= not hasWhitespace end if if (isMacroAllowed(s) = FALSE) then @@ -531,8 +581,23 @@ private function hLoadMacroW _ case CHAR_RPRNT if( prntcnt > 0 ) then prntcnt -= 1 - '' Closing ')'? + + '' determine if we need to add back the right paren ')' + '' if parens were optional and we didn't have any parens + '' for this macro to start with, we need to add back the + '' right paren ')' since it will be needed by the caller. if( prntcnt = 0 ) then + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) = 0 ) then + if( hasParens = FALSE ) then + if( argtb->count >= symbGetDefineParams( s ) ) then + if( pp.invoking > 1 ) then + readdchar = CHAR_RPRNT + end if + end if + end if + end if + + '' Closing ')' exit do end if end if @@ -544,6 +609,27 @@ private function hLoadMacroW _ '' "..." vararg, which just "absorbs" everything '' until the closing ')'. if( prntcnt = 1 ) then + + '' Check if comma is ending the macro list of arguments: + '' if parens are optional and we didn't have any parens + '' for this macro to start with, and we now have enough + '' arguments to satisfy the macro, then we can consider + '' the macro call complete. Add back the comma for the + '' caller. + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) = 0 ) then + if( is_variadic = FALSE ) then + if( hasParens = FALSE ) then + if( argtb->count >= symbGetDefineParams( s ) ) then + if( pp.invoking > 1 ) then + readdchar = CHAR_COMMA + end if + prntcnt = 0 + exit do + end if + end if + end if + end if + if( argtb ) then if( argtb->count = 0 ) then argtb->count = 1 @@ -582,6 +668,8 @@ private function hLoadMacroW _ end select + '' we are still in an argument, so just join the current + '' token to the current argument if( argtb <> NULL ) then if( t.dtype <> FB_DATATYPE_WCHAR ) then DWstrConcatAssignA( argtb->tb(num).textw, t.text ) @@ -778,6 +866,7 @@ private function hLoadDefineW _ '' just load text as-is else + '' arg-less macro? if( symbGetDefineIsArgless( s ) ) then var hasParens = FALSE @@ -785,12 +874,18 @@ private function hLoadDefineW _ '' '('? if( lexCurrentChar( ) = CHAR_LPRNT ) then - hasParens = true - else - '' not an error, macro can be passed as param to other macros - if( (pp.invoking > 0) or ((symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) ) then + hasParens = TRUE + end if + + if( (symbGetDefineFlags( s ) and FB_DEFINE_FLAGS_NEEDPARENS) <> 0 ) then + if( hasParens = FALSE ) then + '' not an error, macro can be passed as param to other macros exit function end if + else + '' parens optional? then whitespace before '(' will determine that the '(' + '' is part of the first argument instead of starting the argument list + hasParens and= not hasWhitespace end if if( hasParens ) then diff --git a/tests/pp/macro_no_parentheses.bas b/tests/pp/macro_no_parentheses.bas index d6e075673..4003b56c2 100644 --- a/tests/pp/macro_no_parentheses.bas +++ b/tests/pp/macro_no_parentheses.bas @@ -2,15 +2,91 @@ SUITE( fbc_tests.pp.macro_no_parentheses ) - # macro m1 ? ( foo, bar ) - foo + bar - # endmacro - TEST( initializer ) + # macro m1 ? ( foo, bar ) + foo + bar + # endmacro + var hello = m1 "hello", "!" var world = m1 "world", "!" - - END_TEST + + END_TEST + + TEST( nested1 ) + + #macro of ? ( __T__ ) + ##__T__ + #endmacro + + #define conc( __A__, __B__ ) __A__##__B__ + + #define tostr( __S__ ) __fb_quote__( __S__ ) + + #macro template ? ( __N__, __T__ ) + tostr( conc( __N__, __T__ ) ) + #endmacro + + type Foo + as integer bar + end type + + dim s as string + + s = template( Foo, of( bar ) ) + CU_ASSERT_EQUAL( s, "Foobar" ) + + s = template( Foo, of bar ) + CU_ASSERT_EQUAL( s, "Foobar" ) + + s = template Foo, of bar + CU_ASSERT_EQUAL( s, "Foobar" ) + + END_TEST + + TEST( nested2 ) + + #define tostr( __S__ ) __fb_quote__( __S__ ) + + #macro foo ? ( a, b ) + __fb_quote__( a b ) + #endmacro + + #macro bar ? ( a, b, c ) + __fb_quote__( a b c ) + #endmacro + + #macro baz ? ( a, b, c ) + __fb_quote__( a b c ) + #endmacro + + dim s as string + dim c as string = "$""1 2 3"" $""4 5 6""" + + s = foo( bar( 1, 2, 3 ), baz( 4, 5, 6 ) ) + CU_ASSERT_EQUAL( s, c ) + + s = foo( bar 1, 2, 3, baz( 4, 5, 6 ) ) + CU_ASSERT_EQUAL( s, c ) + + s = foo( bar( 1, 2, 3 ), baz 4, 5, 6 ) + CU_ASSERT_EQUAL( s, c ) + + s = foo( bar 1, 2, 3, baz 4, 5, 6 ) + CU_ASSERT_EQUAL( s, c ) + + s = foo bar( 1, 2, 3 ), baz( 4, 5, 6 ) + CU_ASSERT_EQUAL( s, c ) + + s = foo bar 1, 2, 3, baz( 4, 5, 6 ) + CU_ASSERT_EQUAL( s, c ) + + s = foo bar( 1, 2, 3 ), baz 4, 5, 6 + CU_ASSERT_EQUAL( s, c ) + + s = foo bar 1, 2, 3, baz 4, 5, 6 + CU_ASSERT_EQUAL( s, c ) + + END_TEST END_SUITE