Tuesday, June 18, 2024

CodeMonkey

context: January 2005 is 4 months earlier than the latest web.archive of Clicker website. It is still lacking important (?) pages such as CodeMonkeyInternals (restructured from the current page ?) or ResourceCmPlugin explaining another thing code monkey was used for.
The CodeMonkey is a [Tool] for code rewriting that is due for Clicker 0.8.20 It will helps writing KDS/IDL code, accessing koLib templates (foreach on koList, cursors, etc), defining koLib & memory classes and much more.

Mechanics of CodeMonkey

The CodeMonkey core is a Perl script that performs input tokenization and manage context stacks (each 'block' has its own context). It comes with a helper package Tokens::Filter.pm that allows one to easily perform high-level pattern matching against operators, etc. Each CodeMonkeyPlugin can be requested separately via the @plugin "name.pl" in the source file.

Rules to write CodeMonkeyPlugin~s

All the 'top-level' code is for initialization

Among other things, it should insert _keyword hooks_ in $context->{hooks} to have plugin functions called when a keyword is encountered. You can also register _terminators_, that is, plugin functions to be called when the input file is completed.

# a skeleton CodeMonkey plugin

use Tokens::Filter;
print STDERR "using skeleton plugin\n";
$context->{hooks}->{skeleton}=['SKELETON',\&myHookFunction];
push @{$context->{terminators}},['SKELETON',\&myTerminator];
true;

sub myHookFunction {
}
sub myTerminator {
}

Hooks receive $mode, $tagname, $codeA, $codeB

The $mode parameter tells you the context where the keyword has been seen. It could be an 'INSTR' or a 'BLOCK'. $tagname returns you the first word that was associated with the hook (here 'SKELETON'). This can be used when multiple similar keywords wish to use the same function but still differenciate the cases.

$codeA and $codeB contains what you need to pass Tokens::Filter to analyze the code that triggered the hook.


sub myHookFunction {
  my ($mode, $tagname)=@_;
  my $code=new Tokens::Filter(@_[2,3]);

  # write your stuff here
}

$context and blocks

Each block construct has its own $context information. Thus when your hook is registered with $mode eq 'BLOCK', you can register local hooks in $context->{hooks}, etc. The "header" of the block (e.g. all the text between the previous instruction/block and the { symbol will used for the "block name" (after tags are processed). A hook can discard the name's output by setting a true value in $context->{naked}

$context->{upper} chains towards the upper-level context information.

controlling INSTRuctions

The hook can decide whether or not a terminating ';' should be written after the instruction's translation by returning a _true_ or _false_ value.

matching text

The Tokens::Filter package can be used to check high-level patterns in the 'running code'. The function to use is $code->match(<offset>, <pattern>). The <pattern> is a list of item types describing what we expect to be on the input:
  • WORD matches an identifier that is not a registered keyword.
  • LITT matches a litteral (e.g. a string/character)
  • TAGG matches any identified keyword
  • OPER matches any sequence of non-alphanum and non block-forming characters. Note that both "+","++","=++","*=&" will be seen as valid OPERators.
  • SLST matches an opening parenthese
  • LIST matches a completed list (pointing towards a specific code sequence).
  • THIS matches the current tagged-keyword
  • TRAN matches a translated item (from a previous hook application)
The type can be followed by a ":<value>" string further restricting possible matches For instance qw(THIS WORD OPER:~= LIST) will match an code sequence like skeleton var=(any thing can be here). The <offset> argument of $code->match tells the relative position of THIS in the pattern.

# let's try to check if we have the expected "skeleton var = (...)" framework.
sub myHookFunction {
  my ($mode, $tagname)=@_;
  my $code=new Tokens::Filter(@_[2,3]);

  # 0 is the position of "THIS" in the list ...
  if ($code->match(0, qw(THIS WORD OPER:~= LIST))) {
     # here we know.
  } else {
     die "unexpected use of 'skeleton'".$code->show;
  }
}

getting/replacing text

Once a pattern has been matched, you can get the text of the different items using $code->content. Each item in the pattern is numbered from _0_ to _N_ and you can retrieve its value with $code->content(i). For instance,

  # retrieve the variable name and the content of the list.
  # note that we get the list content as a raw string.
  # "skeleton myVar = (a,b,c,d,e)" ==> $variable_name eq 'myVar' &&
  #   $list_content eq '(a,b,c,d,e)'
  my $variable_name=$code->content(1);
  my $list_content=$code->content(3);
As the purpose of CodeMonkey is to _rewrite_ text, we could like to replace the actual text by something else. That is (again) done with $code->content() giving the new value as an additionnal argument:

  # rewrite it as "int* myVar[]=..."
  $code->content(0,'int*');
  $code->content(3,'[]=');
Of course, you may use PERL's power for more complex operations, for instance if i wish to have all the items in the $list_content translated so that we extract their address and make an array out of it, it simply means

#  remove parenthesis
$list_content=~ s/^\((.*)\)$/$1/;
#  split the content, assuming a flat list
@list_content= split /,/,$list_content;
@list_content= map { '&'.$_ } @list_content;
$code->content(2,"{".join(',',@list_content)."}");

#now we have "int* myVar[]={&a,&b,&c,&d,&e};

Date: Fri, 14 Jan 2005 06:52:43 -0800 Mime-Version: 1.0 (Produced by PhpWiki 1.3.9) Content-Type: application/x-phpwiki; pagename=CodeMonkey; flags=""; author=PypeClicker; version=3; lastmodified=1105714363; author_id=PypeClicker; markup=2; summary=more%20like%20a%20tutorial; hits=124; charset=iso-8859-1 Content-Transfer-Encoding: binary

ClientCmPlugin

Context: I thought I had lost the clicker wiki forever, or at least, that I couldn't recover the part describing the late "code monkey" mechanics I had developed and used to "ease" development. It seems like some part of it had been saved in some wikidump folder in an obscure location of rsync-only file server of sourceforge. Maybe it isn't the latest version, but let's have a sample of what's in there anyway...
client.pl is a CodeMonkeyPlugin that performs rewrite of [KDS] client/server code, much like SlangSyntax used to do, but taking greater benefit of [IDL]-generated knowledge

Declaring interface we'll use

Each interface will use a *prefix* for its identification. It's important that one give different prefixes for different interfaces, especially if there are methods named the same way in the interfaces. Interface declaration can use either client or using keyword depending on whether you also want a struct kdsClient to be created.

syntax:

using "$servicename$":$interfacename$ as $prefix$;

client "$servicename$":$interfacename$ as $prefix$;
$servicename the path from kds://services to the kdsService (e.g. timing, sys.paging, sys.binterpreter, dev.disk, etc)
$interfacename the name of the declared interface on the service, just as in IDL files
$prefix must be a C-compatible token that will be used to prefix every interface-related things like messages structure, etc. The prefix may vary from one source file to the other.

using and client statement superseeds the need for #include <api/___.api> and should precede any use of the interface (either through implementation or invokation). Moreover they must appear at top-level.

declaring a simple server

The server declaration will need either using or client to be first defined. Each server may implement any number of interfaces but they should all belong to the same service. Server declaration must also appear at top-level

syntax:

server "$servicename$" { (<server_command>|<implementation>)* }

<implementation> ::= implements $interfacename$ { <method>* }
<method> ::= method $methodname$ ( $serverinfotype$* $serverinfovar$, message $name$ ) { <code> }
the method command will generate the appropriate function prototype, using _$prefix$_$methodname$ as function name. You normally don't need to know that name unless you want to do funny KDS bypassing stuff. Functions parameter are available through the message structure (e.g. $name$->$parameter$)

Adding ServerCommands

Since 0.8.20, the "client" plugin is also able to handle server initializaion/termination and activity callback declarations. The syntax is

<server_command>::= <server_vars>|<server_methods>
<server_methods>::= on $eventname$ ($args$) { $code$ }
<server_vars>   ::= with (queue|thread) $varname$ = <value>;

A sample

Let's suppose we defined a 'test:hello' interface with methods void hello(char* who); and void bye(char* who);

@plugin "client.pl"
using "test":hello as greet;
client "display":basic as print;

server "test" {
   implements hello {
      method hello(void* we_dont_care, message m) {
         printStr(&DefaultConsole,"Hello %s!\n",m->who);
         return KDSE_OK;
      }
      method bye(void* we_donT_care, message m) {
         printStr(&DefaultConsole,"L8r, %s...\n",m->who);
         return KDSE_OK;
      }
    }
}

Date: Tue, 19 Oct 2004 08:34:12 -0700 Mime-Version: 1.0 (Produced by PhpWiki 1.3.9) Content-Type: application/x-phpwiki; pagename=ClientCmPlugin; flags=""; author=PypeClicker; version=3; lastmodified=1098200052; author_id=PypeClicker; markup=2; hits=49; charset=iso-8859-1 Content-Transfer-Encoding: binary