Basic-Macros in fbc 1.08

Forum for discussion about the documentation project.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Basic-Macros in fbc 1.08

Post by fxm »

Jeff,

About your reference example:

Code: Select all

#macro assign( sym, expr )
   #define tmp __FB_EVAL__( expr )
   __FB_UNQUOTE__( __FB_EVAL__( "#undef " + sym ) )
   __FB_UNQUOTE__( __FB_EVAL__( "#define " + sym + " " + __FB_QUOTE__( tmp ) ) )
   #undef tmp
#endmacro

#define x

assign( "x", 1 )
print x                  '' 1

assign( "x", x+1 )
print x                  '' 2

assign( "x", cos(1/x) )
print x                  '' 0.877..

assign( "x", "hello" )
print x                  '' hello

assign( "x", x+x )
print x                  '' hellohello
The temporary #define "tmp" seems to be unnecessary:

Code: Select all

#macro assign( sym, expr )
   __FB_UNQUOTE__( __FB_EVAL__( "#undef " + sym ) )
   __FB_UNQUOTE__( __FB_EVAL__( "#define " + sym + " " + __FB_QUOTE__( expr ) ) )
#endmacro

#define x

assign( "x", 1 )
print x                  '' 1

assign( "x", x+1 )
print x                  '' 2

assign( "x", cos(1/x) )
print x                  '' 0.877..

assign( "x", "hello" )
print x                  '' hello

assign( "x", x+x )
print x                  '' hellohello
Are there any cases of "expr" where this temporary #define is needed?


[edit]
Wrong remark (see my next post).
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Basic-Macros in fbc 1.08

Post by coderJeff »

Hmm, I see what you mean.
If expression is functional, like in math y=f(x), the fbc macro facility handles this well.
Furthermore, if the macro expansion is used in a place where an expression is allowed anyway, __fb_eval__ seems to have no value.

I think __fb_eval__ macro would be useful where there is any of the following :
- non functional expression (i.e. side-effects)
- needs to be evaluated (i.e. simplified) before passing it on
- used in a place where fbc would not allow an expression

So, this will be some kind of meta-language programming or generic code generation. Simple use examples probably aren't going to show this.

Frankly, I'm not sure what a good example of usage would be. It seems rather limited.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Basic-Macros in fbc 1.08

Post by fxm »

coderJeff wrote: I think __fb_eval__ macro would be useful where there is any of the following :
- non functional expression (i.e. side-effects)
- needs to be evaluated (i.e. simplified) before passing it on
- used in a place where fbc would not allow an expression
You are right.
Even in your reference example, cos(1/x) must be properly evaluated before be quoted (after the previous assign("x", x+1)), otherwise in that case cos(1/x+1) is taken into account (giving cos(2)) instead of cos(1/(x+1)) (giving cos (1/2)) because operator precedence is not applied by __FB_QUOTE__.

The three __FB_EVAL__ are absolutely mandatory in this assign() macro.
coderJeff wrote:Frankly, I'm not sure what a good example of usage would be. It seems rather limited.
Not as much as you say!
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: Basic-Macros in fbc 1.08

Post by Tourist Trap »

Hello,

I read the whole topic and didn't find if we can now ask a macro to remove a loop in this kind of cases where we are declaring a UDT serially:

Code: Select all

#macro LoopBuildT(n)
    for i as integer = 1 to n
        type T##n
            declare constructor()
            as integer fi
        end type
    next i
#endMacro

LoopBuildT(5)

'C:\Program Files (x86)\FreeBasic\FBIde0.4.6r4\FBIDETEMP.bas(10) error 263: Objects with default structors or methods are only allowed in the module level in 'LoopBuildT(5)'

'-----output (PP)
 for i as integer =1 to 5
 type T5
 declare constructor()
 as integer fi
 end type
 next i

Maybe I missed something here and it is already possible to solve the issue with the old or new features? Can you then tell me how?

If not the case, I would then submit a request for either a capability to remove some structures like loops after the macro execution is done, or I would ask for a new feature able to serialize macro defined UDTs, and other module-level declarations or objects likewise.

Thanks anyway. And also for the new features. Sound very powerful.

ps: This is the constructor here that causes the issue since the below version without it goes well:

Code: Select all

#macro LoopBuildT(n)
    for i as integer = 1 to n
        type T##n
            as integer fi
        end type
    next i
#endMacro

LoopBuildT(5)
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Basic-Macros in fbc 1.08

Post by fxm »

I'm having a hard time figuring out what you want:
- The Type is not declared serially.
- A Type is declared once in the loop, with as typename "T" followed by "Asc(end_value)"
- The error is that a Type declared in a local scope cannot have a member procedure.
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: Basic-Macros in fbc 1.08

Post by Tourist Trap »

fxm wrote:I'm having a hard time figuring out what you want:
- The Type is not declared serially.
- A Type is declared once in the loop, with as typename "T" followed by "Asc(end_value)"
- The error is that a Type declared in a local scope cannot have a member procedure.
Sorry. I can make it simpler. Suppose you want to declare a variable amount of UDTs which main name is T (and suffixed by an index):

Code: Select all

type T1
......
end type

type T2
......
end type

...

type TN
......
end type
Doing this in a loop in a macro won't work because of the local scope induced by the loop.

Code: Select all

for i = 1 to N
      MAKE(T, i)
next i
For now, as you said in bold in your quote, that doesn't work well.
Then, since there is some effort put in extending macros those days, I wondered if it would be possible to solve this issue. I think it would be possible if somehow the preprocessor could get rid of the loop context in this job. In anyway this is probably a unwanted limitation. In my opinion it is useful to be able to define UDTs serially sometimes, and I can't find any workaround.
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Re: Basic-Macros in fbc 1.08

Post by D.J.Peters »

I know what you try (using loop counter in macro) but that make no sense at all.
If all class members are the same (and in a macro loop it would be the same)

type X0
declare sameA ...
as sameB ...
end type

type X1
declare sameA ...
as sameB ...
end type

type X2
declare sameA ...
as sameB ...
end type

you can define the class once as template or proto type
type ProtoType
declare sameA ...
as sameB ...
end type

and then extend it
type Class0 extends ProtoType
end type
type Class1 extends ProtoType
end type
type Class2 extends ProtoType
end type
...

Joshy
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: Basic-Macros in fbc 1.08

Post by Tourist Trap »

D.J.Peters wrote: If all class members are the same (and in a macro loop it would be the same)
Hi Joshy, thanks. However I want the content to be able to also change when needed.

Here is an example where the content changes along with the index. It's with namespaces but it's typically the same kind of issue.
So this works well:

Code: Select all

#macro MAKE(n)
    namespace NS##n
        dim x as zstring ptr = @(#n)
    end namespace
#endmacro

MAKE(1)
MAKE(2)

? *NS1.x
? *NS2.x

'output 1 and 2

#undef MAKE
#undef NS1
#undef NS2
This, however, won't work:

Code: Select all

#macro MAKE(n)
    namespace NS##n
        dim x as zstring ptr = @(#n)
    end namespace
#endmacro

for i as integer = 1 to 2
    MAKE(i)
next i

? *NS1.x
? *NS2.x
The loop scope is interfering. Maybe we could in thefuture have some kind of preprocessor loop that would not induce a local scope?

@fxm
Can we use the Unique ID generator as a workaround?
This -->
__FB_UNIQUEID_PUSH__(ID), __FB_UNIQUEID__(ID), __FB_UNIQUEID_POP__(ID)
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Basic-Macros in fbc 1.08

Post by coderJeff »

fbc pre processor doesn't have any looping capability and recursion is not allowed. You can get a loop like action by unrolling the loop, and passing a macro name as an argument to another macro.

Code: Select all

#macro MACROLOOP1( index, count, macroname )
	#if( index <= count )
		macroname( index )
	#endif
#endmacro

#macro MACROLOOP( count, macroname )
	#if( count < 1 )
		#error "bad MACROLOOP count"
	#elseif( count > 10 )
		#error "bad MACROLOOP count"
	#else
		MACROLOOP1( 1, count, macroname )
		MACROLOOP1( 2, count, macroname )
		MACROLOOP1( 3, count, macroname )
		MACROLOOP1( 4, count, macroname )
		MACROLOOP1( 5, count, macroname )
		MACROLOOP1( 6, count, macroname )
		MACROLOOP1( 7, count, macroname )
		MACROLOOP1( 8, count, macroname )
		MACROLOOP1( 9, count, macroname )
		MACROLOOP1( 10, count, macroname )
	#endif
#endmacro

#macro MAKE( index )
	namespace ns##index
		dim x as zstring ptr = @(#index)
	end namespace
#endmacro

#macro GETNSX( index )
    *ns##index##.x
#endmacro

#macro PRINT_X( index )
	print "ns" & index & ".x = " & GETNSX( index )
#endmacro

MACROLOOP( 7, MAKE )

MACROLOOP( 7, PRINT_X )

print GETNSX( 1 )
print GETNSX( 2 )
print GETNSX( 3 )

adeyblue
Posts: 299
Joined: Nov 07, 2019 20:08

Re: Basic-Macros in fbc 1.08

Post by adeyblue »

coderJeff wrote: __FB_ARG_LEFTOF__( arg, sep ), __FB_ARG_RIGHTOF__( arg, sep )
Returns left or right tokens based on separator
Detecting and using pointer types in macros is difficult since they necessarily have a space in the type name (so the simple approach to generic typing like the following doesn't work)

Code: Select all

#define MakeAContainerOf(UserType) __FB_JOIN__(MyContainer, UserType)
Type MakeAContainerOf(Any Ptr) '' can't have Type MyContainerAny Ptr
    dim c as ...
End Type
With these new capabilities you can...

Code: Select all

#define IS_Ptr(x) ( _
    __FB_QUOTE__( _
        __FB_ARG_LEFTOF__( _
            x, _
            PTR _
        ) _
    ) <> "" _
)

#macro IsItAPtrType(x)
#if Is_Ptr(x)
#Print x is a ptr
#else
#print x isnt a ptr
#endif
#endmacro

IsItAPtrType(Long)
IsItAPtrType(Long Ptr)
IsItAPtrType(Temptress)
...Except in the failure case, it causes compile errors

Code: Select all

C:\shared\FBSamples>c:\freebasic32\fbc.exe pp.bas
pp.bas(21) error 17: Syntax error, expanding: __FB_ARG_LEFTOF__ in 'IsItAPtrType(Long)'
Long isnt a ptr
Long Ptr is a ptr
pp.bas(23) error 17: Syntax error, expanding: __FB_ARG_LEFTOF__ in 'IsItAPtrType(Temptress)'
Temptress isnt a ptr
If there a way to make LEFTOF & RIGHTOF either return nothing and not complain when sep isn't found, or to just return the whole of their input?

Also, wishlist, if we have __ARG_COUNT__, can we get something to extract the args too? I know you can do it manually with existing features so its not super important, but it's a lot of boilerplate to cover each arg position for each possible number of args
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Basic-Macros in fbc 1.08

Post by coderJeff »

adeyblue wrote:If there a way to make LEFTOF & RIGHTOF either return nothing and not complain when sep isn't found, or to just return the whole of their input?
How about this?

__FB_ARG_LEFTOF__( expr, sep [, ret] )
__FB_ARG_RIGHTOF__( expr, sep [, ret] )

by default, if 'ret' is not given return nothing (empty token) if sep not found, otherwise return 'ret' if 'ret' is given and sep not found.
Updated in current fbc/master for 1.08.0 and test written.

About getting Nth argument...
Originally I thought to do '__FB_ARG_GET__( N )', but turns out is really hard to do.
So it's probably going to be '__FB_ARG_GET__( N, args... )' to return the Nth arg of 'args...'
Just trying it now ....

Thanks, it would be nice to have some kind of '__FB_ARG_GET__()' before release.
adeyblue
Posts: 299
Joined: Nov 07, 2019 20:08

Re: Basic-Macros in fbc 1.08

Post by adeyblue »

coderJeff wrote: Thanks, it would be nice to have some kind of '__FB_ARG_GET__()' before release.
Thank you for the update. At the risk of causing much ire though, I went and did the arg one. I remember last time I tried looking up something seemingly simple in the compiler source code I couldn't find it. Where these are implemented was much easier to find and figure out how they workthough, so yeah, I should have done that first before posting, sorry. I'll write some tests and do the PR if you haven't already done it by the time you see this.

Code: Select all

private function hDefArgExtract_cb( byval argtb as LEXPP_ARGTB ptr, byval errnum as integer ptr ) as string

	'' __FB_ARG_EXTRACT__(NUMARG, ARGS...)
	'' Retuns empty string on invalid index, rather than compile error

	var res = ""
	var numStr = hMacro_getArgZ( argtb, 0 )

	if( numStr <> NULL ) then
		'' Is NUMARG a number
		dim numArgLen as Long = Len(*numStr), i as Long, index as ULong = 0
		dim zeroVal As ULong = Asc("0")
		For i = 0 To numArgLen - 1
			if( Not hIsCharNumeric(numStr[i]) ) then
				Exit For
			End If
			index *= 10
			index += (numStr[i] - zeroVal)
		Next
		If i = numArgLen Then
			dim numVarArgs As ULong = argtb->count - 1
			if(index < numVarArgs) then
				var argString = hMacro_getArgZ( argtb, 1 )
				dim varArgs() as string

				hSplitStr(*argString, ",", varArgs())
				res = varArgs(index)
				ZStrFree(argString)
			end if

		else '' NUMARG isn't a number
			*errnum = FB_ERRMSG_SYNTAXERROR
		end if
		ZStrFree(numStr)

	else '' No args
		*errnum = FB_ERRMSG_ARGCNTMISMATCH
	end if
	return res

end function
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Basic-Macros in fbc 1.08

Post by coderJeff »

Cool! You're at least half-way there now, you might as well keep going :)

I like the name '__FB_ARG_EXTRACT__'

It's neat to see a similar pattern in the solution as I did goof around with it for a bit:

Code: Select all

private function hDefArgGet_cb( byval argtb as LEXPP_ARGTB ptr, byval errnum as integer ptr ) as string

	'' __FB_ARG_GET__( N, ARGS... )

	var res = ""
	var argN = hMacro_getArgZ( argtb, 0 )

	if( argN <> NULL ) then
		var n = cuint( *argN )
		if( n >= 1 and n <= argtb->count-1 ) then
			var args = hMacro_getArgZ( argtb, 1 )
			dim argArray() as string
			hSplitStr( *args, ",", argArray() )
			res = argArray(n-1)
			ZStrFree(args)
		else
			res = ""
		end if
	else
		*errnum = FB_ERRMSG_ARGCNTMISMATCH
	end if

	ZstrFree(argN)

	return res

end function
I used a cheap `var n = cuint( *argN )` to get the argcount, can use some other numeric literals, but no test for being a valid number, unfortunately.

Anyway, your code looks good. Look forward to the PR.
fxm
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Basic-Macros in fbc 1.08

Post by fxm »

coderJeff wrote:How about this?

__FB_ARG_LEFTOF__( expr, sep [, ret] )
__FB_ARG_RIGHTOF__( expr, sep [, ret] )

by default, if 'ret' is not given return nothing (empty token) if sep not found, otherwise return 'ret' if 'ret' is given and sep not found.
Updated in current fbc/master for 1.08.0 and test written.
1)
changelog.txt has not been updated accordingly.

2)
Documentation updated:
- KeyPgDdfbargrightof → fxm [added an optional parameter for the default return]
- KeyPgDdfbargleftof → fxm [added an optional parameter for the default return]
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Basic-Macros in fbc 1.08

Post by coderJeff »

Thank-you for the updates on the docs.
We are probably OK on the changelog.txt, these basic-macros are still new to 1.08.0. I can update the changelog to show the sytaxes though.
Post Reply