Skip to content

Commit

Permalink
fbc: basic-macros: improve optional parens in macros
Browse files Browse the repository at this point in the history
- improve the handling of optional parens in function like macros
  by ending a macro argument list on the number of arguments
  expected (for non-variadic macros only where number of
  expected arguments is known).

Example:
		#macro add ? ( __Q__ )
		  ##__Q__
		#endmacro

		#macro concat ? ( __A__, __B__ )
			__A__##__B__
		#endmacro

		#print concat( X, add( Y ) )
		#print concat( X, add Y )
		#print concat X, add Y

		/' Compile Output:
			XY
			XY
			XY
		'/
  • Loading branch information
jayrm committed Feb 5, 2023
1 parent 88dfda3 commit bcc65b4
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 28 deletions.
1 change: 1 addition & 0 deletions changelog.txt
Expand Up @@ -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
Expand Down
139 changes: 117 additions & 22 deletions src/compiler/pp-define.bas
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -778,19 +866,26 @@ private function hLoadDefineW _

'' just load text as-is
else

'' arg-less macro?
if( symbGetDefineIsArgless( s ) ) then
var hasParens = FALSE
var hasWhitespace = lexEatWhitespace( )

'' '('?
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
Expand Down
88 changes: 82 additions & 6 deletions tests/pp/macro_no_parentheses.bas
Expand Up @@ -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

0 comments on commit bcc65b4

Please sign in to comment.