www.digitalmars.com         C & C++   DMDScript  

D - Re: Can we have context functions?

reply "Nic Tiger" <nictiger progtech.ru> writes:
Hi Walter,

Recently I thought a lot of concept which would provide support for logging
and tracking without Cish macroses.

This thoughts led me to the notion of context functions.

DEFINITION. Context function is a function which accepts along  with it's
own parameters several hidden parameters such as FILE, LINE or FUNC. This
hidden parameters are always passed when the function is called and have
values describing the place where call is originated.

Let's suppose that __context keyword enables us to define context function,
and __file, __line, __func are keywords that designate hidden parameters
inside context function.
Now let's look at concepts of using logging and tracking in C and D.

1. Logging - the method to store some information along with description of
it's sourse
============ C

#define LogStr(str)  DoWriteLogStr ( "%s,%d: %s\n", __FILE__, __LINE__,
str )
...
main ()
{
 LogStr ("got here" );
 ...
 LogStr ("got here" );
}

============ D
__context void LogStr ( char[] str )
{
 printf ( "%.*s,%d: %.*s\n", __file, __line, str );
}
...
main ()
{
 LogStr ("got here" );
 ...
 LogStr ("got here" );
}

2. Tracking - the method to determine who called particular function
============ C
#define _malloc(s)  malloc_tracked (s, __FILE__, __LINE__)

void *malloc_tracked ( size_t size, char *file, int line )
{
 printf ("malloc(%d) from %s,%d\n", size, file, line );
 return malloc ( size );
}

main ()
{
 void *p = _malloc ( 10 );
}

============ D
__context void *malloc ( size_t size )
{
 printf ("malloc(%d) from %s,%d\n", size, __file, __line );

 // do allocation here...
}

main()
{
 void *p = malloc ( 10 );
}

========================== end of examples

Note that in D we not only got rid of preprocessor usage and have the
identical behaviour, but also eliminated intermediate function
malloc_tracked in the second example.

As I see the implementation for this concept is trivial - pass context
parameters to function just like you pass object pointer to class methods.
The keywords used bear imagination. I don't think that names __context and
others are perfect, but they are relevant to problem being solved.
Another issue is that we need support via version statement in order to find
out whether current function is context or not. Then we could rewrite the
second example as:
/*__context*/ void *malloc ( size_t size )
{
 version(__context) {
  printf ("malloc(%d) from %s,%d\n", size, __file, __line );
 }

 // do allocation here...
}

And now we can turn functions into context functions and vice versa easily.

I would like you to consider the examples given and say your word about it.
Any critique and afterthought are welcome.

Nic Tiger.
Mar 15 2003
next sibling parent reply "Nic Tiger" <nictiger progtech.ru> writes:
More thoughts: we can specify context parameters passed with this simple
syntax.

__context(__file, __line) void LogStr ( char[] str )
{
 printf ( "%.*s,%d: %.*s\n", __file, __line, str );
}

Then only declared context parameter should be available inside context
function. This simplifies compiler logic - simply pass context parameters
function wanted.

As for inapplicable context parameters (such as __class when calling not
from member-function), the can be passed either NULL or "" (void string).

I really would like to here anyone's opinion on this.

Nic Tiger.

"Nic Tiger" <nictiger progtech.ru> сообщил/сообщила в новостях следующее:
news:b503u0$22db$1 digitaldaemon.com...
 Hi Walter,

 Recently I thought a lot of concept which would provide support for

 and tracking without Cish macroses.

 This thoughts led me to the notion of context functions.

 DEFINITION. Context function is a function which accepts along  with it's
 own parameters several hidden parameters such as FILE, LINE or FUNC. This
 hidden parameters are always passed when the function is called and have
 values describing the place where call is originated.

 Let's suppose that __context keyword enables us to define context

 and __file, __line, __func are keywords that designate hidden parameters
 inside context function.
 Now let's look at concepts of using logging and tracking in C and D.

 1. Logging - the method to store some information along with description

 it's sourse
 ============ C

 #define LogStr(str)  DoWriteLogStr ( "%s,%d: %s\n", __FILE__, __LINE__,
 str )
 ...
 main ()
 {
  LogStr ("got here" );
  ...
  LogStr ("got here" );
 }

 ============ D
 __context void LogStr ( char[] str )
 {
  printf ( "%.*s,%d: %.*s\n", __file, __line, str );
 }
 ...
 main ()
 {
  LogStr ("got here" );
  ...
  LogStr ("got here" );
 }

 2. Tracking - the method to determine who called particular function
 ============ C
 #define _malloc(s)  malloc_tracked (s, __FILE__, __LINE__)

 void *malloc_tracked ( size_t size, char *file, int line )
 {
  printf ("malloc(%d) from %s,%d\n", size, file, line );
  return malloc ( size );
 }

 main ()
 {
  void *p = _malloc ( 10 );
 }

 ============ D
 __context void *malloc ( size_t size )
 {
  printf ("malloc(%d) from %s,%d\n", size, __file, __line );

  // do allocation here...
 }

 main()
 {
  void *p = malloc ( 10 );
 }

 ========================== end of examples

 Note that in D we not only got rid of preprocessor usage and have the
 identical behaviour, but also eliminated intermediate function
 malloc_tracked in the second example.

 As I see the implementation for this concept is trivial - pass context
 parameters to function just like you pass object pointer to class methods.
 The keywords used bear imagination. I don't think that names __context and
 others are perfect, but they are relevant to problem being solved.
 Another issue is that we need support via version statement in order to

 out whether current function is context or not. Then we could rewrite the
 second example as:
 /*__context*/ void *malloc ( size_t size )
 {
  version(__context) {
   printf ("malloc(%d) from %s,%d\n", size, __file, __line );
  }

  // do allocation here...
 }

 And now we can turn functions into context functions and vice versa

 I would like you to consider the examples given and say your word about

 Any critique and afterthought are welcome.

 Nic Tiger.

Mar 19 2003
parent reply Farmer <itsFarmer. freenet.de> writes:
Hi.

Your idea of passing "compiler context" implicitly to functions seems very 
useful. I think, it could cover all common uses of the C preprocessor 
constants (__file, __line, etc.).

Here are my critiques and afterthoughts ;-)


What about making it look like a usual function, that has some parameters 
that are set implicitly by the compiler ?

E.g. instead of

 __context(__file, __line) void LogStr ( char[] str )
 {
  printf ( "%.*s,%d: %.*s\n", __file, __line, str );
 }

void LogStr ( char[] str, char[] __file, int __line ) { printf ( "%.*s,%d: %.*s\n", __file, __line, str ); } or shorter void LogStr ( char[] str, __file, __line ); or similar to C default parameters void LogStr ( char[] str, char[] fileName=__file, int lineNo=__line ) { printf ( "%.*s,%d: %.*s\n", fileName, lineNo, str ); } or void LogStr ( char[] str, __file fileName, __line lineNo) { printf ( "%.*s,%d: %.*s\n", fileName, lineNo, str ); } or use a context structure in order to safe keywords and make it more extendable: void LogStr( char[] str, __context ) { printf ( "%.*s,%d: %.*s\n", __context.file, __context.line, str ); } maybe you should be able to pass only some information of the context structure: void LogStr( char[] str, __context.file) { printf ( "%.*s,%d: %.*s\n", __context.file, __context.line, str ); } // above, __context.line is no longer valid. In some examples, I used the common D types int and char[]; to avoid any conflicts with argument overloading, the special context parameters should have their own type. Should be easy with typedef. One drawback compared with your proposed syntax is that versioning would require more typing, possibly cluttering interfaces a lot: version (tracedMalloc) void *malloc ( size_t size, __file, __line ) { printf ("malloc(%d) from %s,%d\n", size, __file, __line ); } else void *malloc ( size_t size) { printf ("malloc(%d) from %s,%d\n", size); } instead of just
/*__context*/ void *malloc ( size_t size )
{
 version(__context) {
  printf ("malloc(%d) from %s,%d\n", size, __file, __line );
 }

But hey, you are cheating! __context is just commented out! That won't scale up to many functions. Farmer.
Mar 24 2003
parent reply "Nic Tiger" <nictiger progtech.ru> writes:
Thanks for reply.

I don't think it is convenient for programmer and for compiler vendor too to
specify context functions via adding some magic names of parameters to
function declaration.
We don't add __class_object to all member functions, for example. Instead,
we have it implicit and have access to it via this.
Context functions are quite the same.

And also we can make __context keyword working just like extern(C), for
example;

__context(__file, __line) void LogStr ( char[] str ) {...}

or

__context(__file, __line) {
 void LogStr ( char[] str ) {...}
 void Log2Str ( char[] str ) {...}
 // and so on.
}

Anyway, adding hidden parameters to function parameters list seems muddy for
me.
How can I understand that I should not pass this parameters and they are
passed automatically by compiler?

On overloading issue I would say that context functions are just like member
function (from implementation point of view). We have overloaded member
functions? Why do we get troubles with context ones?

And also I would prohibited such declarations:
   __context(__file, __line) void LogStr ( char[] str ) {...}
   __context(__file) void LogStr ( char[] str ) {...}
because they are ambiguous.

Anyway I would like to hear Walter's opinion on that.

Nic Tiger.
"Farmer" <itsFarmer. freenet.de> сообщил/сообщила в новостях следующее:
news:Xns9348E7CA7DABEitsFarmer 63.105.9.61...
 Hi.

 Your idea of passing "compiler context" implicitly to functions seems very
 useful. I think, it could cover all common uses of the C preprocessor
 constants (__file, __line, etc.).

 Here are my critiques and afterthoughts ;-)


 What about making it look like a usual function, that has some parameters
 that are set implicitly by the compiler ?

 E.g. instead of

 __context(__file, __line) void LogStr ( char[] str )
 {
  printf ( "%.*s,%d: %.*s\n", __file, __line, str );
 }

void LogStr ( char[] str, char[] __file, int __line ) { printf ( "%.*s,%d: %.*s\n", __file, __line, str ); } or shorter void LogStr ( char[] str, __file, __line ); or similar to C default parameters void LogStr ( char[] str, char[] fileName=__file, int lineNo=__line ) { printf ( "%.*s,%d: %.*s\n", fileName, lineNo, str ); } or void LogStr ( char[] str, __file fileName, __line lineNo) { printf ( "%.*s,%d: %.*s\n", fileName, lineNo, str ); } or use a context structure in order to safe keywords and make it more extendable: void LogStr( char[] str, __context ) { printf ( "%.*s,%d: %.*s\n", __context.file, __context.line, str ); } maybe you should be able to pass only some information of the context structure: void LogStr( char[] str, __context.file) { printf ( "%.*s,%d: %.*s\n", __context.file, __context.line, str ); } // above, __context.line is no longer valid. In some examples, I used the common D types int and char[]; to avoid any conflicts with argument overloading, the special context parameters should have their own type. Should be easy with typedef. One drawback compared with your proposed syntax is that versioning would require more typing, possibly cluttering interfaces a lot: version (tracedMalloc) void *malloc ( size_t size, __file, __line ) { printf ("malloc(%d) from %s,%d\n", size, __file, __line ); } else void *malloc ( size_t size) { printf ("malloc(%d) from %s,%d\n", size); } instead of just
/*__context*/ void *malloc ( size_t size )
{
 version(__context) {
  printf ("malloc(%d) from %s,%d\n", size, __file, __line );
 }

But hey, you are cheating! __context is just commented out! That won't scale up to many functions. Farmer.

Mar 25 2003
parent reply Farmer <itsFarmer. freenet.de> writes:
"Nic Tiger" <nictiger progtech.ru> wrote in
news:b5q2jt$1v3h$1 digitaldaemon.com: 

 Thanks for reply.
 
 I don't think it is convenient for programmer and for compiler vendor
 too to specify context functions via adding some magic names of
 parameters to function declaration.
 We don't add __class_object to all member functions, for example.
 Instead, we have it implicit and have access to it via this.
 Context functions are quite the same.
 
 And also we can make __context keyword working just like extern(C),
 for example;

Yes, making the __context keyord like extern(C) is quite appealing. I could think of __context(...) as a kind of calling convention, which it is from a implementation point of view.
 
 __context(__file, __line) void LogStr ( char[] str ) {...}
 
 or
 
 __context(__file, __line) {
  void LogStr ( char[] str ) {...}
  void Log2Str ( char[] str ) {...}
  // and so on.
 }
 
 Anyway, adding hidden parameters to function parameters list seems
 muddy for me.
 How can I understand that I should not pass this parameters and they
 are passed automatically by compiler?

You should be able to pass these "context" parameters; a D compiler is merely kind enough to provide them for you. Sometimes an extra level of control is handy. It also makes calling a "context" function from outside D very obvious. Of course, when using your syntax, language interoperability isn't a problem, either.
 
 On overloading issue I would say that context functions are just like
 member function (from implementation point of view). We have
 overloaded member functions? Why do we get troubles with context ones?

Right. There aren't any problems. I just commented on my own style of syntax. When using my syntax, overloading ambiguities could arise, as they do for default parameters in C++. By using non-standard types, these ambiguities can practically be avoided.
 
 And also I would prohibited such declarations:
    __context(__file, __line) void LogStr ( char[] str ) {...}
    __context(__file) void LogStr ( char[] str ) {...}
 because they are ambiguous.
 
 Anyway I would like to hear Walter's opinion on that.

Ok, let's wait. Farmer.
Mar 26 2003
parent reply "David Leonard" <david.leonard itee.uq.edu.au> writes:
How about a library module that allows access to the call stack?
Help stop feeping creaturism.

import callstack;

void LogStr(char[] str)
{
    callstack.Frame f = callstack.getPreviousFrame(2); // caller's caller
    printf("%s:%d:%s(): %s\n", f.sourceFile, f.lineNo, f.symbol, str);
}
Apr 01 2003
next sibling parent "Sean L. Palmer" <palmer.sean verizon.net> writes:
You're basically making it mandatory for D to include a .map file or line #
debug info in every executable.

I doubt that'll fly, but yeah it'd be handy sometimes.  Who knows?

Sean

"David Leonard" <david.leonard itee.uq.edu.au> wrote in message
news:b6dn1k$cil$1 digitaldaemon.com...
 How about a library module that allows access to the call stack?
 Help stop feeping creaturism.

 import callstack;

 void LogStr(char[] str)
 {
     callstack.Frame f = callstack.getPreviousFrame(2); // caller's caller
     printf("%s:%d:%s(): %s\n", f.sourceFile, f.lineNo, f.symbol, str);
 }

Apr 02 2003
prev sibling parent reply "Nic Tiger" <nictiger progtech.ru> writes:
I want context functions to be fast and they *must not* interfere
performance of all other code.
Your approach is surely the way, but it could be very inefficient.

I just want to get fast and useful tool for development.

Nic Tiger.

"David Leonard" <david.leonard itee.uq.edu.au> сообщил/сообщила в новостях
следующее: news:b6dn1k$cil$1 digitaldaemon.com...
 How about a library module that allows access to the call stack?
 Help stop feeping creaturism.

 import callstack;

 void LogStr(char[] str)
 {
     callstack.Frame f = callstack.getPreviousFrame(2); // caller's caller
     printf("%s:%d:%s(): %s\n", f.sourceFile, f.lineNo, f.symbol, str);
 }

Apr 02 2003
parent "Sean L. Palmer" <palmer.sean verizon.net> writes:
The devil is in the details.  Map files and debug info do not slow down the
rest of the code, they're just big.  Doing it some other way such as writing
special info to the stack frames would slow the whole game down.

How would you implement this library?  There are libraries like this for
C++.

Don't we all want that?

Sean

"Nic Tiger" <nictiger progtech.ru> wrote in message
news:b6f530$1g2r$1 digitaldaemon.com...
 I want context functions to be fast and they *must not* interfere
 performance of all other code.
 Your approach is surely the way, but it could be very inefficient.

 I just want to get fast and useful tool for development.

 Nic Tiger.

 "David Leonard" <david.leonard itee.uq.edu.au> сообщил/сообщила в новостях
 следующее: news:b6dn1k$cil$1 digitaldaemon.com...
 How about a library module that allows access to the call stack?
 Help stop feeping creaturism.

 import callstack;

 void LogStr(char[] str)
 {
     callstack.Frame f = callstack.getPreviousFrame(2); // caller's


     printf("%s:%d:%s(): %s\n", f.sourceFile, f.lineNo, f.symbol, str);
 }


Apr 02 2003
prev sibling parent "Walter" <walter digitalmars.com> writes:
I think the concept is a good one, and addresses a problem I've wrestled
with for a while.
Apr 02 2003