Files
gnucash/src/gnome/dialog-sxsincelast.c
T
Joshua Sled 1e36965ded Don't write <gnc:template-transactions> section unless there is data to write [1.6/1.7 interop-issue brought up on #gnucash].
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@4907 57a11ea4-9604-0410-9ed3-97b8803252fd
2001-07-06 08:29:36 +00:00

768 lines
28 KiB
C

/********************************************************************\
* dialog-sxsincelast.c - "since last run" dialog. *
* Copyright (c) 2001 Joshua Sled <jsled@asynchronous.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
#include "config.h"
#include <gnome.h>
#include <glib.h>
#include "gnc-engine-util.h"
#include "FileDialog.h"
#include "gnc-book.h"
#include "Transaction.h"
#include "Group.h"
#include "gnc-numeric.h"
#include "SchedXaction.h"
#include "gnc-component-manager.h"
#include "SplitLedger.h"
#include "gnc-ui-util.h"
#include "gnc-exp-parser.h"
#include "dialog-utils.h"
#include "dialog-sxsincelast.h"
#define DIALOG_SXSINCELAST_CM_CLASS "dialog-sxsincelast"
static short module = MOD_SX;
typedef struct _toCreateTransaction {
SchedXaction *sx;
GDate *date;
gint clistRow;
} toCreateTransaction;
typedef struct _sxSinceLastData {
GtkWidget *sxsincelastDlg;
GladeXML *gxml;
GList /* <toCreateTransaction*> */ *transList;
} sxSinceLastData;
typedef struct _reminderTuple {
GDate *endDate;
GDate *occurDate;
} reminderTuple;
static void sxsincelast_init( sxSinceLastData *sxsld );
static void sxsincelast_populate( sxSinceLastData *sxsld );
static void sxsincelast_close_handler( gpointer ud );
static void sxsincelast_ok_clicked( GtkButton *b, gpointer ud );
static void sxsincelast_cancel_clicked( GtkButton *b, gpointer ud );
static void sxsincelast_destroy( GtkObject *o, gpointer ud );
static void _create_transactions_on( SchedXaction *sx, GDate *gd );
static void processAutoCreateList( GList *, sxSinceLastData *sxsld, SchedXaction * );
static void processToCreateList( GList *, sxSinceLastData *sxsld, SchedXaction * );
static void processReminderList( GList *, sxSinceLastData *sxsld, SchedXaction * );
/* int parse_vars_from_formula( const char *formula, GHashTable *varHash ); */
void
gnc_ui_sxsincelast_guile_wrapper( char *foo )
{
gnc_ui_sxsincelast_dialog_create();
}
void
gnc_ui_sxsincelast_dialog_create(void)
{
sxSinceLastData *sxsld = g_new0( sxSinceLastData, 1 );
sxsld->gxml = gnc_glade_xml_new( "sched-xact.glade", "Real Since-Last-Run Dialog" );
sxsld->sxsincelastDlg = glade_xml_get_widget( sxsld->gxml, "Real Since-Last-Run Dialog" );
sxsincelast_init( sxsld );
}
static void
sxsincelast_init( sxSinceLastData *sxsld )
{
GtkWidget *o;
int i;
struct widgetSignalHandlerTuple {
char *name;
char *signal;
void (*handlerFn)();
} widgets[] = {
{ "ok_button", "clicked", sxsincelast_ok_clicked },
{ "cancel_button", "clicked", sxsincelast_cancel_clicked },
{ NULL, NULL, NULL }
};
gnc_register_gui_component( DIALOG_SXSINCELAST_CM_CLASS,
NULL,
sxsincelast_close_handler,
sxsld->sxsincelastDlg );
gtk_signal_connect( GTK_OBJECT(sxsld->sxsincelastDlg), "destroy",
GTK_SIGNAL_FUNC( sxsincelast_destroy ), sxsld );
for ( i=0; widgets[i].name != NULL ; i++ ) {
o = glade_xml_get_widget( sxsld->gxml, widgets[i].name );
gtk_signal_connect( GTK_OBJECT(o), widgets[i].signal,
GTK_SIGNAL_FUNC(widgets[i].handlerFn),
sxsld );
}
sxsincelast_populate( sxsld );
gtk_widget_show_all( sxsld->sxsincelastDlg );
}
static void
_generate_instances( SchedXaction *sx,
GDate *end,
GList **instanceList )
{
GDate gd, *gdToReturn;
char tmpBuf[26];
gd = xaccSchedXactionGetNextInstance( sx );
while ( g_date_valid(&gd)
&& g_date_compare( &gd, end ) <= 0 ) {
g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", &gd );
DEBUG( "Adding instance %s", tmpBuf );
gdToReturn = g_date_new();
*gdToReturn = gd;
*instanceList = g_list_append( *instanceList, gdToReturn );
gd = xaccSchedXactionGetInstanceAfter( sx, &gd );
}
if ( ! g_date_valid( &gd ) ) {
PERR( "Should be added to dead-list..." );
}
}
static void
_free_gdate_list_elts( gpointer data, gpointer user_data )
{
g_date_free( (GDate*)data );
}
static void
_free_reminderTuple_list_elts( gpointer data, gpointer user_data )
{
g_date_free( ((reminderTuple*)data)->occurDate );
g_free( (reminderTuple*)data );
}
static void
processAutoCreateList( GList *autoCreateList, sxSinceLastData *sxsld, SchedXaction *sx )
{
char tmpBuf[26];
int row;
GtkCList *cl;
gboolean autoCreateState, notifyState;
char *rowText[2];
row = 0;
/*
get the "automagically created and notification requested"
register, and create the entires.
For now, this is a clist...
*/
cl = GTK_CLIST( glade_xml_get_widget( sxsld->gxml, "auto_create_clist" ) );
gtk_clist_freeze( cl );
while ( autoCreateList ) {
g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y",
(GDate*)autoCreateList->data );
xaccSchedXactionGetAutoCreate( sx, &autoCreateState, &notifyState );
_create_transactions_on( sx, (GDate*)autoCreateList->data );
if ( notifyState ) {
rowText[0] = xaccSchedXactionGetName( sx );
rowText[1] = malloc( sizeof(gchar) * 26 ); /* FIXME */
g_date_strftime( rowText[1], 25, "%a, %b %e, %Y",
(GDate*)autoCreateList->data );
gtk_clist_insert( cl, row++, rowText );
}
autoCreateList = autoCreateList->next;
}
gtk_clist_thaw( cl );
}
static void
processToCreateList( GList *toCreateList, sxSinceLastData *sxsld, SchedXaction *sx )
{
GtkCList *clist;
int row;
char *rowText[2];
row = 0;
if ( toCreateList == NULL )
return;
clist = GTK_CLIST( glade_xml_get_widget( sxsld->gxml, "to_create_clist" ) );
do {
/* add to clist [ahem... register... ahem] */
rowText[0] = xaccSchedXactionGetName( sx );
rowText[1] = malloc( sizeof(char) * 25 ); /* FIXME */
g_date_strftime( rowText[1], 25, "%a, %b %e, %Y", (GDate*)toCreateList->data );
gtk_clist_insert( clist, row++, rowText );
} while ( (toCreateList = toCreateList->next) );
}
static void
processReminderList( GList *reminderList, sxSinceLastData *sxsld, SchedXaction *sx )
{
GtkCList *clist;
int row;
char *rowText[3];
reminderTuple *rt;
/* . need
. orig end date
. this instance date
. sx
*/
row = 0;
if ( reminderList == NULL )
return;
clist = GTK_CLIST( glade_xml_get_widget( sxsld->gxml, "reminders_clist" ) );
do {
rt = (reminderTuple*)reminderList->data;
/* add to clist [ahem... register... ahem] */
rowText[0] = xaccSchedXactionGetName( sx );
rowText[1] = malloc( sizeof(gchar) * 25 ); /* FIXME */
g_date_strftime( rowText[1], 25, "%a, %b %e, %Y", rt->occurDate );
rowText[2] = malloc( sizeof(gchar) * 5 ); /* FIXME */
sprintf( rowText[2], "%d",
(g_date_julian(rt->occurDate) - g_date_julian(rt->endDate)) );
gtk_clist_insert( clist, row++, rowText );
} while ( (reminderList = reminderList->next) );
}
static void
sxsincelast_populate( sxSinceLastData *sxsld )
{
GList *sxList, *instanceList;
GList *autoCreateList, *toCreateList, *reminderList;
SchedXaction *sx;
GDate end, endPlusReminders;
GDate *instDate;
gint daysInAdvance;
gboolean autocreateState, notifyState;
gchar *rowText[3];
GtkWidget *w;
gint autoCreateRow, toCreateRow, remindersRow;
reminderTuple *rt;
autoCreateList = toCreateList = reminderList = NULL;
autoCreateRow = toCreateRow = remindersRow = 0;
sxList = gnc_book_get_schedxactions( gncGetCurrentBook() );
if ( sxList == NULL ) {
DEBUG( "No scheduled transactions to populate." );
return;
}
do {
sx = (SchedXaction*)sxList->data;
g_date_set_time( &end, time(NULL) );
daysInAdvance = xaccSchedXactionGetAdvanceCreation( sx );
g_date_add_days( &end, daysInAdvance );
endPlusReminders = end;
daysInAdvance = xaccSchedXactionGetAdvanceReminder( sx );
g_date_add_days( &endPlusReminders, daysInAdvance );
if (0) {
char tmpBuf[26];
g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", &end );
DEBUG( "We'll generate the appropriate instances, now for "
"SX \"%s\" with end time %s...",
xaccSchedXactionGetName(sx),
tmpBuf );
}
instanceList = NULL;
_generate_instances( sx, &endPlusReminders, &instanceList );
if ( instanceList == NULL )
continue;
xaccSchedXactionGetAutoCreate( sx, &autocreateState, &notifyState );
do {
instDate = (GDate*)instanceList->data;
if ( 0 ) {
char tmpBuf[26];
g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", instDate );
DEBUG( "SX \"%s\" instance on %s",
xaccSchedXactionGetName(sx), tmpBuf );
}
if ( (g_date_compare( &end, &endPlusReminders ) != 0)
&& (g_date_compare( &end, instDate ) <= 0) ) {
/* DEBUG( "\tIs a reminder" ); */
rt = g_new0( reminderTuple, 1 );
rt->endDate = &end;
rt->occurDate = instDate;
reminderList = g_list_append( reminderList, rt );
} else {
if ( autocreateState ) {
/* DEBUG( "\tauto-create" ); */
autoCreateList = g_list_append( autoCreateList,
instDate );
} else {
/* DEBUG( "\tAdding to create list" ); */
toCreateList = g_list_append( toCreateList,
instDate );
}
}
} while ( (instanceList = instanceList->next) );
/* process the lists */
processAutoCreateList( autoCreateList, sxsld, sx );
processToCreateList ( toCreateList, sxsld, sx );
processReminderList ( reminderList, sxsld, sx );
g_list_foreach( autoCreateList, _free_gdate_list_elts, NULL );
g_list_free( autoCreateList );
g_list_foreach( reminderList, _free_reminderTuple_list_elts, NULL );
g_list_free( reminderList );
/* FIXME: freeup the toCreate list at a more appropriate
time. */
} while ( (sxList = sxList->next) );
}
static void
sxsincelast_close_handler( gpointer ud )
{
gnome_dialog_close( GNOME_DIALOG( ((sxSinceLastData*)ud)->sxsincelastDlg ) );
}
static void
free_elts( gpointer data, gpointer user_data )
{
g_free( data );
}
static void
sxsincelast_ok_clicked( GtkButton *b, gpointer ud )
{
sxSinceLastData *sxsld;
GtkWidget *dlg;
GtkWidget *o;
GtkCList *cl;
time_t gdeDate;
GList *sxList;
GNCBook *book;
SchedXaction *sx;
GDate gd, *endDate;
gchar buf[1024];
gint row;
char *rowText[2];
toCreateTransaction *tct;
sxsld = (sxSinceLastData*)ud;
sxsincelast_close_handler( sxsld );
/* FIXME: the SX/date list already has been generated; we should use that. */
#if 0
o = glade_xml_get_widget( sxsld->gxml, "next_date" );
gdeDate = gnome_date_edit_get_date( GNOME_DATE_EDIT(o) );
DEBUG( "Okay... I should run with a date of: %s", ctime(&gdeDate) );
/* destroy the previous transactions
destroy all the toCreateTransactions */
if ( sxsld->transList != NULL ) {
g_list_foreach( sxsld->transList, free_elts, NULL );
g_list_free( sxsld->transList );
sxsld->transList = NULL;
}
book = gncGetCurrentBook();
sxList = gnc_book_get_schedxactions( book );
if ( sxList == NULL ) {
PERR( "No scheduled transactions to play with\n" );
return;
}
endDate = g_date_new();
g_date_set_time( endDate, gdeDate );
o = glade_xml_get_widget( sxsld->gxml, "replace_with_register" );
cl = GTK_CLIST(o);
gtk_clist_clear( cl );
g_date_clear( &gd, 1 );
row = 0;
do {
sx = (SchedXaction*)sxList->data;
// this is really "last-run-date", right?
g_date_set_time( &gd, time(NULL) );
gd = xaccSchedXactionGetInstanceAfter( sx, &gd );
while ( g_date_compare( &gd, endDate ) <= 0
&& g_date_valid( &gd ) ) {
g_date_strftime( buf, 1023, "%a, %b %e, %Y", &gd );
/* add to clist [ahem... register... ahem] */
rowText[0] = xaccSchedXactionGetName( sx );
rowText[1] = malloc( sizeof(char) * 256 ); /* FIXME */
g_date_strftime( rowText[1], 255, "%a, %b %e, %Y", &gd );
tct = g_new0( toCreateTransaction, 1 );
tct->sx = sx;
tct->date = g_date_new();
*tct->date = gd;
tct->clistRow = row;
sxsld->transList = g_list_append( sxsld->transList, tct );
gtk_clist_insert( cl, row, rowText );
row += 1;
gd = xaccSchedXactionGetInstanceAfter( sx, &gd );
}
} while ( (sxList = sxList->next) );
g_date_free( endDate );
#endif
}
static void
sxsincelast_cancel_clicked( GtkButton *o, gpointer ud )
{
sxsincelast_close_handler( ud );
}
static void
sxsincelast_destroy( GtkObject *o, gpointer ud )
{
DEBUG( "nuttin' doin...\n" );
}
static gboolean
_create_each_transaction( Transaction *t, void *d )
{
Transaction *newT;
GDate *gd;
GList *sList;
GList *osList;
Split *split;
kvp_frame *split_kvpf;
kvp_value *kvp_val;
gboolean errFlag;
errFlag = FALSE;
DEBUG( "I'm seeing Transaction \"%s\"",
xaccTransGetDescription( t ) );
gd = (GDate*)d;
newT = xaccMallocTransaction();
xaccTransBeginEdit( newT );
/* the action and description/memo are in the template */
gnc_copy_trans_onto_trans( t, newT, FALSE, FALSE );
/* the date is new [gd] */
xaccTransSetDate( newT,
g_date_day( gd ),
g_date_month( gd ),
g_date_year( gd ) );
/* the accounts and amounts are in the kvp_frames of the splits. */
osList = xaccTransGetSplitList( t );
sList = xaccTransGetSplitList( newT );
if ( (osList == NULL) || (sList == NULL) ) {
PERR( "\tseen transaction w/o splits. :(" );
return FALSE;
}
do {
split = (Split*)sList->data;
/* FIXME: Ick. This assumes that the split lists will be
ordered identically. :( I think it's fair to say they
will, but I'd rather not have to count on it. --jsled */
split_kvpf = xaccSplitGetSlots( (Split*)osList->data );
DEBUG( "\tProcessing Split \"%s\"",
xaccSplitGetMemo( split ) );
DEBUG( "\tkvp_frame: %s\n",
kvp_frame_to_string( split_kvpf ) );
/* from-transaction of splits */
{
GUID *acct_guid;
Account *acct;
/* contains the guid of the split's actual account. */
kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/xfrm" );
if ( kvp_val == NULL ) {
PERR( "Null kvp_val for xfrm" );
}
acct_guid = kvp_value_get_guid( kvp_val );
acct = xaccAccountLookup( acct_guid );
DEBUG( "Got account with name \"%s\"",
xaccAccountGetName( acct ) );
/* xaccSplitSetAccount( split, acct ); */
xaccAccountInsertSplit( acct, split );
}
/* credit/debit formulas */
{
char *str;
gnc_numeric credit_num;
gnc_numeric debit_num;
gnc_numeric final;
int gncn_error;
kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/credit_formula" );
str = kvp_value_get_string( kvp_val );
credit_num = gnc_numeric_create( 0, 1 );
if ( str != NULL ) {
/* parse_vars_from_formula( str, NULL ); */
xaccParseAmount( str, TRUE, &credit_num, NULL );
/* string_to_gnc_numeric( str, &credit_num ); */
DEBUG( "gnc_numeric::credit: \"%s\" -> \"%s\"",
str, gnc_numeric_to_string( credit_num ) );
}
kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/debit_formula" );
str = kvp_value_get_string( kvp_val );
debit_num = gnc_numeric_create( 0, 1 );
if ( str != NULL ) {
/* parse_vars_from_formula( str, NULL ); */
xaccParseAmount( str, TRUE, &debit_num, NULL );
/* string_to_gnc_numeric( str, &debit_num ); */
DEBUG( "gnc_numeric::debit: \"%s\" -> \"%s\"",
str, gnc_numeric_to_string( debit_num ) );
}
final = gnc_numeric_sub_fixed( credit_num, debit_num );
gncn_error = gnc_numeric_check( final );
if ( gncn_error != GNC_ERROR_OK ) {
PERR( "Error %d in final gnc_numeric value", gncn_error );
errFlag = TRUE;
break;
}
DEBUG( "gnc_numeric::final: \"%s\"",
gnc_numeric_to_string( final ) );
xaccSplitSetValue( split, final );
}
#if 0
kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/shares" );
kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/amnt" );
#endif /* 0 */
} while ( (sList = sList->next) && (osList = osList->next) );
if ( errFlag ) {
PERR( "Some error in new transaction creation..." );
xaccTransRollbackEdit( newT );
} else {
xaccTransCommitEdit( newT );
}
return TRUE;
}
static void
_create_transactions_on( SchedXaction *sx, GDate *gd )
{
AccountGroup *ag;
Account *acct;
char *id;
char tmpBuf[26];
{
g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", gd );
DEBUG( "Creating transactions on %s for %s",
tmpBuf, xaccSchedXactionGetName( sx ) );
}
ag = gnc_book_get_template_group( gncGetCurrentBook() );
id = guid_to_string( xaccSchedXactionGetGUID(sx) );
acct = xaccGetAccountFromName( ag, id );
DEBUG( "Got account \"%s\"\n",
xaccAccountGetName( acct ) );
g_free( id );
xaccAccountForEachTransaction( acct,
_create_each_transaction,
gd );
xaccSchedXactionSetLastOccurDate( sx, gd );
}
#if 0
/**
* Parses in-fix mathematical formulas using the standard operators
* [+-/%*], and '(', ')' grouping.
*
* Any strings are placed in the GHashTable as variables. The value of
* each key in the hash-table is a struct of the form:
* { const char *varStr;
* gint idx;
* gint len; };
*
**/
int
parse_vars_from_formula( const char *formula, GHashTable *varHash )
{
gnc_numeric num_foo;
char *foo;
GList *list;
gnc_exp_parser_init();
if ( ! gnc_exp_parser_parse( formula, &num_foo, &foo ) ) {
PERR( "Error parsing at \"%s\": %s\n",
foo, gnc_exp_parser_error_string() );
}
PINFO( "Successful parse...\n" );
list = gnc_exp_parser_get_variable_names();
if ( list == NULL ) {
PINFO( "NULL variable list\n" );
} else {
do {
PINFO( "Variable \"%s\"\n",
list->data );
} while ( (list = list->next) );
}
gnc_exp_parser_shutdown();
}
#endif /* 0 */
#if 0
GScanner *varScanner;
GTokenType tok;
varScanner = g_scanner_new( NULL );
g_scanner_set_scope( varScanner, 0 );
g_scanner_freeze_symbol_table( varScanner );
g_scanner_scope_add_symbol( varScanner, 0, "(", (gpointer)"left-paren" );
g_scanner_scope_add_symbol( varScanner, 0, ")", (gpointer)"right-paren" );
g_scanner_scope_add_symbol( varScanner, 0, "+", (gpointer)"plus" );
g_scanner_scope_add_symbol( varScanner, 0, "-", (gpointer)"minus" );
g_scanner_scope_add_symbol( varScanner, 0, "/", (gpointer)"div" );
g_scanner_scope_add_symbol( varScanner, 0, "*", (gpointer)"mult" );
g_scanner_thaw_symbol_table( varScanner );
g_scanner_input_text( varScanner, formula, strlen( formula ) );
do {
tok = g_scanner_get_next_token( varScanner );
printf( "tok: " );
switch ( varScanner->token ) {
case G_TOKEN_EOF:
printf( "EOF" ); break;
case G_TOKEN_LEFT_PAREN:
printf( "(" ); break;
case G_TOKEN_RIGHT_PAREN:
printf( ")" ); break;
case G_TOKEN_LEFT_CURLY:
printf( "{" ); break;
case G_TOKEN_RIGHT_CURLY:
printf( "}" ); break;
case G_TOKEN_LEFT_BRACE:
printf( "[" ); break;
case G_TOKEN_RIGHT_BRACE:
printf( "]" ); break;
case G_TOKEN_EQUAL_SIGN:
printf( "=" ); break;
case G_TOKEN_COMMA:
printf( "," ); break;
case G_TOKEN_NONE:
printf( "NONE" ); break;
case G_TOKEN_ERROR:
printf( "ERROR(%d)", varScanner->value.v_error ); break;
case G_TOKEN_CHAR:
printf( "CHAR(%c)", varScanner->value.v_char ); break;
case G_TOKEN_BINARY:
printf( "BINARY" ); break;
case G_TOKEN_OCTAL:
printf( "OCTAL(%ul)", varScanner->value.v_octal ); break;
case G_TOKEN_INT:
printf( "INT(%ul)", varScanner->value.v_int ); break;
case G_TOKEN_HEX:
printf( "HEX(%ul)", varScanner->value.v_hex ); break;
case G_TOKEN_FLOAT:
printf( "FLOAT(%f)", varScanner->value.v_float ); break;
case G_TOKEN_STRING:
printf( "STRING(%s)", varScanner->value.v_string ); break;
case G_TOKEN_SYMBOL:
printf( "SYMBOL(%s)", (gchar*)varScanner->value.v_symbol ); break;
case G_TOKEN_IDENTIFIER:
printf( "IDENT(%s)", varScanner->value.v_identifier ); break;
case G_TOKEN_IDENTIFIER_NULL:
printf( "NULL_IDENT" ); break;
case G_TOKEN_COMMENT_SINGLE:
case G_TOKEN_COMMENT_MULTI:
printf( "COMMENT(%s)", varScanner->value.v_comment ); break;
case G_TOKEN_LAST:
printf( "END" ); break;
default:
printf( "UNK" ); break;
};
printf( "\n" );
} while ( (varScanner->token != G_TOKEN_LAST) &&
(varScanner->token != G_TOKEN_EOF) &&
(varScanner->token != G_TOKEN_NONE) );
return 1;
/*
"0.33 * ( base + ld ) + (0.25 * internet)":
tok: FLOAT(0.330000)
tok: UNK
tok: )
tok: IDENT(base)
tok: UNK
tok: IDENT(ld)
tok: (
tok: UNK
tok: )
tok: FLOAT(0.250000)
tok: UNK
tok: IDENT(internet)
tok: (
tok: EOF
*/
#endif /* 0 */