2003-07-06 15:01:13 +00:00
|
|
|
#!/usr/bin/perl
|
2004-02-01 20:03:26 +00:00
|
|
|
##
|
2007-12-28 01:47:25 +00:00
|
|
|
## Copyright (C) 2002-2008, Marcelo E. Magallon <mmagallo[]debian org>
|
|
|
|
## Copyright (C) 2002-2008, Milan Ikits <milan ikits[]ieee org>
|
2004-02-01 20:03:26 +00:00
|
|
|
##
|
|
|
|
## This program is distributed under the terms and conditions of the GNU
|
|
|
|
## General Public License Version 2 as published by the Free Software
|
|
|
|
## Foundation or, at your option, any later version.
|
2003-07-06 15:01:13 +00:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
sub compile_regex
|
|
|
|
{
|
|
|
|
my $regex = join('', @_);
|
|
|
|
return qr/$regex/
|
|
|
|
}
|
|
|
|
|
|
|
|
my @sections = (
|
|
|
|
"Name",
|
|
|
|
"Name Strings?",
|
|
|
|
"New Procedures and Functions",
|
2014-09-18 10:51:56 +00:00
|
|
|
"New Tokens.*", # Optional (GL/WGL/GLX/...) suffix
|
2012-05-27 22:13:29 +00:00
|
|
|
"Additions to Chapter.*",
|
2003-07-06 15:01:13 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
my %typemap = (
|
|
|
|
bitfield => "GLbitfield",
|
|
|
|
boolean => "GLboolean",
|
|
|
|
# fsck up in EXT_vertex_array
|
|
|
|
Boolean => "GLboolean",
|
|
|
|
byte => "GLbyte",
|
|
|
|
clampd => "GLclampd",
|
|
|
|
clampf => "GLclampf",
|
|
|
|
double => "GLdouble",
|
|
|
|
enum => "GLenum",
|
|
|
|
# Intel fsck up
|
|
|
|
Glenum => "GLenum",
|
|
|
|
float => "GLfloat",
|
2005-01-29 04:36:24 +00:00
|
|
|
half => "GLhalf",
|
2003-07-06 15:01:13 +00:00
|
|
|
int => "GLint",
|
|
|
|
short => "GLshort",
|
|
|
|
sizei => "GLsizei",
|
|
|
|
ubyte => "GLubyte",
|
|
|
|
uint => "GLuint",
|
|
|
|
ushort => "GLushort",
|
|
|
|
DMbuffer => "void *",
|
2008-10-27 01:23:19 +00:00
|
|
|
# Nvidia video output fsck up
|
|
|
|
int64EXT => "GLint64EXT",
|
|
|
|
uint64EXT=> "GLuint64EXT",
|
2003-07-06 15:01:13 +00:00
|
|
|
|
2004-01-04 14:20:17 +00:00
|
|
|
# ARB VBO introduces these.
|
2003-07-06 15:01:13 +00:00
|
|
|
|
2007-12-28 02:04:26 +00:00
|
|
|
sizeiptr => "GLsizeiptr",
|
|
|
|
intptr => "GLintptr",
|
2003-07-06 15:01:13 +00:00
|
|
|
sizeiptrARB => "GLsizeiptrARB",
|
|
|
|
intptrARB => "GLintptrARB",
|
|
|
|
|
2003-09-26 11:34:59 +00:00
|
|
|
# ARB shader objects introduces these, charARB is at least 8 bits,
|
|
|
|
# handleARB is at least 32 bits
|
|
|
|
charARB => "GLcharARB",
|
2004-01-04 14:20:17 +00:00
|
|
|
handleARB => "GLhandleARB",
|
|
|
|
|
2012-05-26 05:07:59 +00:00
|
|
|
char => "GLchar",
|
|
|
|
|
2009-08-05 14:11:26 +00:00
|
|
|
# OpenGL 3.2 and GL_ARB_sync
|
|
|
|
|
|
|
|
int64 => "GLint64",
|
|
|
|
uint64 => "GLuint64",
|
|
|
|
sync => "GLsync",
|
|
|
|
|
2018-10-09 10:04:51 +00:00
|
|
|
# GL_EXT_EGL_image_storage
|
|
|
|
|
|
|
|
eglImageOES => "GLeglImageOES",
|
|
|
|
|
2010-05-21 19:15:29 +00:00
|
|
|
# AMD_debug_output
|
|
|
|
|
|
|
|
DEBUGPROCAMD => "GLDEBUGPROCAMD",
|
|
|
|
|
2010-07-29 13:33:05 +00:00
|
|
|
# ARB_debug_output
|
|
|
|
|
|
|
|
DEBUGPROCARB => "GLDEBUGPROCARB",
|
|
|
|
|
2012-08-06 14:17:50 +00:00
|
|
|
# KHR_debug
|
|
|
|
|
|
|
|
DEBUGPROC => "GLDEBUGPROC",
|
|
|
|
|
2016-04-08 23:51:57 +00:00
|
|
|
VULKANPROCNV => "GLVULKANPROCNV",
|
|
|
|
|
2010-06-29 23:58:41 +00:00
|
|
|
vdpauSurfaceNV => "GLvdpauSurfaceNV",
|
|
|
|
|
2003-07-06 15:01:13 +00:00
|
|
|
# GLX 1.3 defines new types which might not be available at compile time
|
|
|
|
|
|
|
|
#GLXFBConfig => "void*",
|
|
|
|
#GLXFBConfigID => "XID",
|
|
|
|
#GLXContextID => "XID",
|
|
|
|
#GLXWindow => "XID",
|
|
|
|
#GLXPbuffer => "XID",
|
|
|
|
|
|
|
|
# Weird stuff to some SGIX extension
|
|
|
|
|
|
|
|
#GLXFBConfigSGIX => "void*",
|
|
|
|
#GLXFBConfigIDSGIX => "XID",
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
my %voidtypemap = (
|
2003-10-27 05:28:55 +00:00
|
|
|
void => "GLvoid",
|
2003-07-06 15:01:13 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
my %taboo_tokens = (
|
|
|
|
GL_ZERO => 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
# list of function definitions to be ignored, unless they are being defined in
|
|
|
|
# the given spec. This is an ugly hack arround the fact that people writing
|
|
|
|
# spec files seem to shut down all brain activity while they are at this task.
|
|
|
|
#
|
|
|
|
# This will be moved to its own file eventually.
|
|
|
|
#
|
|
|
|
# (mem, 2003-03-19)
|
|
|
|
|
|
|
|
my %fnc_ignore_list = (
|
|
|
|
"BindProgramARB" => "ARB_vertex_program",
|
|
|
|
"ColorSubTableEXT" => "EXT_color_subtable",
|
|
|
|
"DeleteProgramsARB" => "ARB_vertex_program",
|
|
|
|
"GenProgramsARB" => "ARB_vertex_program",
|
|
|
|
"GetProgramEnvParameterdvARB" => "ARB_vertex_program",
|
|
|
|
"GetProgramEnvParameterfvARB" => "ARB_vertex_program",
|
|
|
|
"GetProgramLocalParameterdvARB" => "ARB_vertex_program",
|
|
|
|
"GetProgramLocalParameterfvARB" => "ARB_vertex_program",
|
|
|
|
"GetProgramStringARB" => "ARB_vertex_program",
|
|
|
|
"GetProgramivARB" => "ARB_vertex_program",
|
|
|
|
"IsProgramARB" => "ARB_vertex_program",
|
|
|
|
"ProgramEnvParameter4dARB" => "ARB_vertex_program",
|
|
|
|
"ProgramEnvParameter4dvARB" => "ARB_vertex_program",
|
|
|
|
"ProgramEnvParameter4fARB" => "ARB_vertex_program",
|
|
|
|
"ProgramEnvParameter4fvARB" => "ARB_vertex_program",
|
|
|
|
"ProgramLocalParameter4dARB" => "ARB_vertex_program",
|
|
|
|
"ProgramLocalParameter4dvARB" => "ARB_vertex_program",
|
|
|
|
"ProgramLocalParameter4fARB" => "ARB_vertex_program",
|
|
|
|
"ProgramLocalParameter4fvARB" => "ARB_vertex_program",
|
|
|
|
"ProgramStringARB" => "ARB_vertex_program",
|
2018-10-09 10:04:51 +00:00
|
|
|
"EGLImageTargetTexture2DOES" => "OES_EGL_image",
|
|
|
|
"FramebufferTextureOES" => "GL_OES_geometry_shader",
|
|
|
|
"PatchParameteriOES" => "GL_OES_tessellation_shader",
|
|
|
|
"PointSizePointerOES" => "GL_OES_point_size_array",
|
|
|
|
"LockArraysEXT" => "EXT_compiled_vertex_array",
|
|
|
|
"UnlockArraysEXT" => "EXT_compiled_vertex_array",
|
|
|
|
"CoverageMaskNV" => "NV_coverage_sample",
|
|
|
|
"CoverageOperationNV" => "NV_coverage_sample",
|
2009-12-31 17:05:56 +00:00
|
|
|
"glXCreateContextAttribsARB" => "ARB_create_context_profile",
|
|
|
|
"wglCreateContextAttribsARB" => "WGL_ARB_create_context_profile",
|
2003-07-06 15:01:13 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
my %regex = (
|
2004-06-27 22:34:11 +00:00
|
|
|
eofnc => qr/(?:\);?$|^$)/, # )$ | );$ | ^$
|
2003-07-06 15:01:13 +00:00
|
|
|
extname => qr/^[A-Z][A-Za-z0-9_]+$/,
|
2010-03-05 18:19:59 +00:00
|
|
|
none => qr/^\(none\)$/,
|
2004-06-27 22:34:11 +00:00
|
|
|
function => qr/^(.+) ([a-z][a-z0-9_]*) \((.+)\)$/i,
|
2018-10-10 06:38:56 +00:00
|
|
|
prefix => qr/^(?:[aw]?gl|glX|egl)/, # gl | agl | wgl | glX
|
|
|
|
tprefix => qr/^(?:[AW]?GL|GLX|EGL)_/, # GL_ | AGL_ | WGL_ | GLX_
|
2003-07-06 15:01:13 +00:00
|
|
|
section => compile_regex('^(', join('|', @sections), ')$'), # sections in spec
|
2016-07-08 11:54:56 +00:00
|
|
|
token => qr/^([A-Z0-9][A-Z0-9_x]*):?\s+((?:0x)?[0-9A-Fa-f]+(u(ll)?)?)(|\s[^\?]*)$/, # define tokens
|
2003-07-06 15:01:13 +00:00
|
|
|
types => compile_regex('\b(', join('|', keys %typemap), ')\b'), # var types
|
|
|
|
voidtype => compile_regex('\b(', keys %voidtypemap, ')\b '), # void type
|
|
|
|
);
|
|
|
|
|
2004-06-27 22:34:11 +00:00
|
|
|
# reshapes the the function declaration from multiline to single line form
|
2003-07-06 15:01:13 +00:00
|
|
|
sub normalize_prototype
|
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
local $_ = join(" ", @_);
|
|
|
|
s/\s+/ /g; # multiple whitespace -> single space
|
2008-10-27 01:23:19 +00:00
|
|
|
s/\<.*\>//g; # remove <comments> from direct state access extension
|
2009-11-11 23:52:12 +00:00
|
|
|
s/\<.*$//g; # remove incomplete <comments> from direct state access extension
|
2012-10-24 19:07:29 +00:00
|
|
|
s#/\*.*\*/##g; # remove /* ... */ comments
|
2004-06-27 22:34:11 +00:00
|
|
|
s/\s*\(\s*/ \(/; # exactly one space before ( and none after
|
2009-12-31 17:05:56 +00:00
|
|
|
s/\s*\)\s*/\)/; # no space before or after )
|
2004-06-27 22:34:11 +00:00
|
|
|
s/\s*\*([a-zA-Z])/\* $1/; # "* identifier"
|
|
|
|
s/\*wgl/\* wgl/; # "* wgl"
|
|
|
|
s/\*glX/\* glX/; # "* glX"
|
|
|
|
s/\.\.\./void/; # ... -> void
|
|
|
|
s/;$//; # remove ; at the end of the line
|
|
|
|
return $_;
|
2003-07-06 15:01:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Ugly hack to work arround the fact that functions are declared in more
|
|
|
|
# than one spec file.
|
|
|
|
sub ignore_function($$)
|
|
|
|
{
|
|
|
|
return exists($fnc_ignore_list{$_[0]}) && $fnc_ignore_list{$_[0]} ne $_[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
sub parse_spec($)
|
|
|
|
{
|
|
|
|
my $filename = shift;
|
|
|
|
my $extname = "";
|
|
|
|
my $vendortag = "";
|
|
|
|
my @extnames = ();
|
|
|
|
my %functions = ();
|
|
|
|
my %tokens = ();
|
|
|
|
|
|
|
|
my $section = "";
|
|
|
|
my @fnc = ();
|
|
|
|
|
|
|
|
my %proc = (
|
|
|
|
"Name" => sub {
|
|
|
|
if (/^([a-z0-9]+)_([a-z0-9_]+)/i)
|
|
|
|
{
|
|
|
|
$extname = "$1_$2";
|
|
|
|
$vendortag = $1;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
"Name Strings" => sub {
|
2004-06-27 22:34:11 +00:00
|
|
|
# Add extension name to extension list
|
2010-03-05 18:19:59 +00:00
|
|
|
|
|
|
|
# Initially use $extname if (none) specified
|
|
|
|
if (/$regex{none}/)
|
|
|
|
{
|
|
|
|
$_ = $extname;
|
|
|
|
}
|
|
|
|
|
2004-06-27 22:34:11 +00:00
|
|
|
if (/$regex{extname}/)
|
|
|
|
{
|
|
|
|
# prefix with "GL_" if prefix not present
|
|
|
|
s/^/GL_/ unless /$regex{tprefix}/o;
|
|
|
|
# Add extension name to extension list
|
|
|
|
push @extnames, $_;
|
|
|
|
}
|
2003-07-06 15:01:13 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"New Procedures and Functions" => sub {
|
2004-06-27 22:34:11 +00:00
|
|
|
# if line matches end of function
|
|
|
|
if (/$regex{eofnc}/)
|
2003-07-06 15:01:13 +00:00
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
# add line to function declaration
|
2003-07-06 15:01:13 +00:00
|
|
|
push @fnc, $_;
|
|
|
|
|
2004-06-27 22:34:11 +00:00
|
|
|
# if normalized version of function looks like a function
|
2003-07-06 15:01:13 +00:00
|
|
|
if (normalize_prototype(@fnc) =~ /$regex{function}/)
|
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
# get return type, name, and arguments from regex
|
2003-07-06 15:01:13 +00:00
|
|
|
my ($return, $name, $parms) = ($1, $2, $3);
|
|
|
|
if (!ignore_function($name, $extname))
|
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
# prefix with "gl" if prefix not present
|
2003-07-06 15:01:13 +00:00
|
|
|
$name =~ s/^/gl/ unless $name =~ /$regex{prefix}/;
|
2004-06-27 22:34:11 +00:00
|
|
|
# is this a pure GL function?
|
|
|
|
if ($name =~ /^gl/ && $name !~ /^glX/)
|
|
|
|
{
|
|
|
|
# apply typemaps
|
|
|
|
$return =~ s/$regex{types}/$typemap{$1}/og;
|
2014-01-08 23:22:37 +00:00
|
|
|
$return =~ s/GLvoid/void/og;
|
|
|
|
$return =~ s/void\*/void */og;
|
2004-06-27 22:34:11 +00:00
|
|
|
$parms =~ s/$regex{types}/$typemap{$1}/og;
|
|
|
|
$parms =~ s/$regex{voidtype}/$voidtypemap{$1}/og;
|
2014-01-08 23:22:37 +00:00
|
|
|
$parms =~ s/GLvoid/void/og;
|
|
|
|
$parms =~ s/ void\* / void */og;
|
2004-06-27 22:34:11 +00:00
|
|
|
}
|
|
|
|
# add to functions hash
|
2003-07-06 15:01:13 +00:00
|
|
|
$functions{$name} = {
|
|
|
|
rtype => $return,
|
|
|
|
parms => $parms,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2004-06-27 22:34:11 +00:00
|
|
|
# reset function declaration
|
2003-07-06 15:01:13 +00:00
|
|
|
@fnc = ();
|
|
|
|
} elsif ($_ ne "" and $_ ne "None") {
|
2004-06-27 22:34:11 +00:00
|
|
|
# if not eof, add line to function declaration
|
2003-07-06 15:01:13 +00:00
|
|
|
push @fnc, $_
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
"New Tokens" => sub {
|
|
|
|
if (/$regex{token}/)
|
|
|
|
{
|
|
|
|
my ($name, $value) = ($1, $2);
|
2004-06-27 22:34:11 +00:00
|
|
|
# prefix with "GL_" if prefix not present
|
2003-07-06 15:01:13 +00:00
|
|
|
$name =~ s/^/GL_/ unless $name =~ /$regex{tprefix}/;
|
2004-06-27 22:34:11 +00:00
|
|
|
# Add (name, value) pair to tokens hash, unless it's taboo
|
2003-07-06 15:01:13 +00:00
|
|
|
$tokens{$name} = $value unless exists $taboo_tokens{$name};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2004-06-27 22:34:11 +00:00
|
|
|
# Some people can't read, the template clearly says "Name String_s_"
|
2003-07-06 15:01:13 +00:00
|
|
|
$proc{"Name String"} = $proc{"Name Strings"};
|
|
|
|
|
|
|
|
# Open spec file
|
|
|
|
open SPEC, "<$filename" or return;
|
|
|
|
|
|
|
|
# For each line of SPEC
|
|
|
|
while(<SPEC>)
|
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
# Delete trailing newline character
|
2003-07-06 15:01:13 +00:00
|
|
|
chomp;
|
2004-06-27 22:34:11 +00:00
|
|
|
# Remove trailing white spaces
|
2003-07-06 15:01:13 +00:00
|
|
|
s/\s+$//;
|
2004-06-27 22:34:11 +00:00
|
|
|
# If starts with a capital letter, it must be a new section
|
2003-07-06 15:01:13 +00:00
|
|
|
if (/^[A-Z]/)
|
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
# Match section name with one of the predefined names
|
|
|
|
$section = /$regex{section}/o ? $1 : "default";
|
2003-07-06 15:01:13 +00:00
|
|
|
} else {
|
2004-06-27 22:34:11 +00:00
|
|
|
# Line is internal to a section
|
|
|
|
# Remove leading whitespace
|
|
|
|
s/^\s+//;
|
|
|
|
# Call appropriate section processing function if it exists
|
2003-07-06 15:01:13 +00:00
|
|
|
&{$proc{$section}} if exists $proc{$section};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
close SPEC;
|
|
|
|
|
|
|
|
return ($extname, \@extnames, \%tokens, \%functions);
|
|
|
|
}
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
my @speclist = ();
|
|
|
|
my %extensions = ();
|
|
|
|
|
|
|
|
my $ext_dir = shift;
|
2018-10-09 10:04:51 +00:00
|
|
|
my $reg_http = "https://www.khronos.org/registry/OpenGL/extensions/";
|
2003-07-06 15:01:13 +00:00
|
|
|
|
|
|
|
# Take command line arguments or read list from file
|
|
|
|
if (@ARGV)
|
|
|
|
{
|
|
|
|
@speclist = @ARGV;
|
|
|
|
} else {
|
|
|
|
local $/; #???
|
|
|
|
@speclist = split "\n", (<>);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $spec (sort @speclist)
|
|
|
|
{
|
|
|
|
my ($extname, $extnames, $tokens, $functions) = parse_spec($spec);
|
|
|
|
|
|
|
|
foreach my $ext (@{$extnames})
|
|
|
|
{
|
2004-06-27 22:34:11 +00:00
|
|
|
my $info = "$ext_dir/" . $ext;
|
|
|
|
open EXT, ">$info";
|
2010-03-05 18:19:59 +00:00
|
|
|
print EXT $ext . "\n"; # Extension name
|
|
|
|
my $specname = $spec;
|
2018-10-09 10:04:51 +00:00
|
|
|
$specname =~ s/OpenGL-Registry\/extensions\///;
|
2010-03-05 18:19:59 +00:00
|
|
|
print EXT $reg_http . $specname . "\n"; # Extension info URL
|
|
|
|
print EXT $ext . "\n"; # Extension string
|
2015-08-29 22:55:10 +00:00
|
|
|
print EXT "\n"; # Resuses nothing by default
|
2004-06-27 22:34:11 +00:00
|
|
|
|
|
|
|
my $prefix = $ext;
|
|
|
|
$prefix =~ s/^(.+?)(_.+)$/$1/;
|
2014-07-25 23:14:36 +00:00
|
|
|
foreach my $token (sort {
|
|
|
|
if (${$tokens}{$a} eq ${$tokens}{$b}) {
|
|
|
|
$a cmp $b
|
|
|
|
} else {
|
|
|
|
if (${$tokens}{$a} =~ /_/) {
|
|
|
|
if (${$tokens}{$b} =~ /_/) {
|
|
|
|
$a cmp $b
|
|
|
|
} else {
|
|
|
|
-1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (${$tokens}{$b} =~ /_/) {
|
|
|
|
1
|
|
|
|
} else {
|
2016-07-08 11:54:56 +00:00
|
|
|
if (${$tokens}{$a} =~ /u(ll)?$/) {
|
|
|
|
if (${$tokens}{$b} =~ /u(ll)?$/) {
|
|
|
|
$a cmp $b
|
|
|
|
} else {
|
|
|
|
-1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (${$tokens}{$b} =~ /u(ll)?$/) {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
if (hex ${$tokens}{$a} eq hex ${$tokens}{$b})
|
|
|
|
{
|
|
|
|
$a cmp $b
|
|
|
|
} else {
|
|
|
|
hex ${$tokens}{$a} <=> hex ${$tokens}{$b}
|
|
|
|
}
|
|
|
|
}
|
2014-09-27 01:34:59 +00:00
|
|
|
}
|
2014-07-25 23:14:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} keys %{$tokens})
|
|
|
|
{
|
2004-12-06 08:14:52 +00:00
|
|
|
if ($token =~ /^$prefix\_.*/i)
|
2004-06-27 22:34:11 +00:00
|
|
|
{
|
2008-10-26 16:07:59 +00:00
|
|
|
print EXT "\t" . $token . " " . ${\%{$tokens}}{$token} . "\n";
|
2004-06-27 22:34:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach my $function (sort keys %{$functions})
|
|
|
|
{
|
|
|
|
if ($function =~ /^$prefix.*/i)
|
|
|
|
{
|
|
|
|
print EXT "\t" . ${$functions}{$function}{rtype} . " " . $function . " (" . ${$functions}{$function}{parms} . ")" . "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close EXT;
|
2003-07-06 15:01:13 +00:00
|
|
|
}
|
|
|
|
}
|