1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
<title>Asymptote Proposal - External Modules</title>
<style><!--
--></style>
</head>
<body>
<h1>Asymptote Proposal &mdash External Modules</h1>
<h2>Overview</h2>
<p>External modules allow users to extend Asymptote by calling functions
written in another programming language.</p>
<p>Users do this by writing a .asyc file, which contains a mix of
Asymptote code and code from another language, say C++. Then, a program
is run which produces a .asy file and a C++ source file. The C++ file is
compiled to produce a shared library file. Then, the .asy file can be
imported in Asymptote to use the externally defined features.</p>
<p>This spec is describes a proposed feature that has not yet been
implemented. It is incomplete, and does not address all of the issues
involved in implementing the feature.</p>
<h2>Example</h2>
<p>Let’s look at a simple example that shows off the main features.
Asymptote currently doesn’t offer a way to read the contents of a
directory. This would be useful if, say, we wanted to make a series of
graphs for every .csv file in a directory.</p>
<pre>
/*****
* dir.asyc
* Andy Hammerlindl 2007/09/11
*
* An example for the proposed external module support in Asymptote. This reads
* the contents of a directory via the POSIX commands.
*
* Example usage in asymptote:
* access dir;
* dir.entry[] entries= dir.open('.');
* for (dir.entry e : entries)
* write(e.name);
*****/
// Verbatim code will appear in the c++ or asy file (as specified) interleaved
// in the same order as it appears here.
verbatim c++ {
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
// asy.h is included by default (needed for hidden code, anyway).
// Asymptote-specific types, such as array below, are in the asy namespace.
using namespace asy;
}
// Define a new opaque type in asy which is internally represented by struct
// dirent *. This is too messy to expose to users of the module, so define
// everything as private.
private asytype const struct dirent *entry_t;
private int entry_d_ino(entry_t e) {
return (Int)e->d_ino;
}
private int entry_d_off(entry_t e) {
return (Int)e->d_off;
}
private int entry_d_reclen(entry_t e) {
return (Int)e->reclen;
}
private string entry_d_type(entry_t e) {
return string( /*length*/ 1, e->d_type);
}
private string entry_d_name(entry_t e) {
return string(e->d_name);
}
// Define an asy structure to expose the information. These steps are annoying,
// but straightforward, and not too hard to plow through.
verbatim asy {
struct entry {
restricted int ino;
restricted int off;
restricted int reclen;
restricted int type;
restricted string name;
void operator init(entry_t e) {
ino=entry_d_ino(e);
off=entry_d_off(e);
reclen=entry_d_reclen(e);
type=entry_d_type(e);
name=entry_d_name(e);
}
}
}
// Given the name of a directory, return an array of entries. Return 0
// (a null array) on error.
private entry_t[] base_read(string name)
{
DIR *dir=opendir(name.c_str());
// TODO: Add standard style of error reporting.
if (dir == NULL)
return 0;
// Create the array structure.
// array is derived from gc, so will be automatically memory-managed.
array *a=new array();
struct dirent *entry;
while (entry=readdir(dir))
a->push<struct dirent *>(entry);
// The loop has exited, either by error, or after reading the entire
// directory. Check before closedir(), in case that call resets errno.
if (errno != 0) {
closedir(dir);
return 0;
}
closedir(dir);
return a;
}
verbatim asy {
private entry[] cleanEntries(entry_t[] raw_entries) {
if (raw_entries) {
entry[] entries;
for (entry_t e : raw_entries)
entries.push(entry(e));
return entries;
}
return null;
}
entry[] read(string name) {
return cleanEntries(base_read(name));
}
}
</pre>
<h2>Type Mappings</h2>
<p>Types in Asymptote do not directly relate to types in C++, but there
is a partial mapping between them. The header file
<code>asymptote.h</code> provides typedefs for the primitive asymptote
types. For instance <code>string</code> in Asymptote maps to the C++
class <code>asy::string</code> which is a variant of
<code>std::string</code> and <code>real</code> to <code>asy::real</code>
which is a basic floating point type (probably <code>double</code>).
Because <code>int</code> is a reserved word in C++, the Asymptote type
<code>int</code> is mapped to <code>asy::Int</code> which is one of the
basic signed numeric types in C++ (currently 64 bit).
<code>asy::pair</code> is a class that implements complex numbers. In
the first version of the external module implementation, these will be
the only primitive types with mappings, but eventually all of them will
be added.</p>
<p>All Asymptote arrays, regardless of the cell type, are mapped to
<code>asy::array *</code> where <code>asy::array</code> is a C++ class.
The cells of the array are of the type <code>asy::item</code> which can
hold any Asymptote data type. Items can be constructed from any C++
type. Once constructed, the value of an item can be retrieved by the
function <code>template<typename T> T get(const item&)</code>.
Calling <code>get</code> on an item using the wrong type generates a
runtime error. </p>
<pre>
// Examples of using item.
item x((asy::Int)2);
item y(3.4);
item z=new array;
item w=(asy::real)3.4;
cout << get<asy::Int>(x);
cout << get<double>(y);
x=y; // x now stores a double.
cout << get<double>(x);
cout << get<asy::real>(w);
</pre>
<p>The <code>asy::array</code> class implements, at a minimum, the
methods:</p>
<ul>
<li><code>size_t size()</code>
which returns the number of elements,</li>
<li><code>template <typename T> T read(size_t i) const</code>
which returns the i-th element, interpreted as being of type t.</li>
<li><code>template <typename T> void push(item i)</code>
adds the item to the end of the array.</li>
</ul>
<p>It allows access to elements of the array as items by
<code>operator[]</code>. We may specify that <code>asy::array</code>
be a model of the Random Access Container in the C++ Standard Template
Library. It is currently implemented as a subclass of an STL
vector.</p>
<pre>
// Example of a C++ function that doubles the entries in an array of integers.
using namespace asy;
void doubler(array *a) {
assert(a);
size_t length=a->size();
for (size_t i=0; i<length; ++i) {
Int x=a->read<Int>(i); // This is shorthand for get<Int>((*a)[i]).
a[i]=2*x; // The type of 2*x is also Int, so this will enter
// the item as the proper type.
}
}
</pre>
<p>Users can map new Asymptote types to their own custom C++ types using
Opaque Type Declarations, explained below.</p>
<h2>Syntactic Features</h2>
<p>A .asyc file is neither an asy file with some C++ in it, nor a C++
with some asy code in it. It can only contain a small number of
specific constructs:</p>
<ul>
<li>Comments</li>
<li>Function Definitions</li>
<li>Verbatim Code Block</li>
<li>Opaque Type Declaration</li>
</ul>
<!-- Move to after constructs -->
<p>Each component may produce code for either the .asy file, the .cc
file, or both. The pieces of code produced by each construct appears in
the output file in the same order as the constructs in the .asyc. For
example, if a function definition occurs before a verbatim Asymptote
code block, we can be sure that the function is defined and can be used
in that block. Similarly, if a verbatim C++ block occurs before a
function definition, then the body of the function can use features
declared in the verbatim section.</p>
<h3>Comments</h3>
<p>C++/Asymptote style comments using <code>/* */</code> or
<code>//</code> are allowed at the top level. These do not affect the
definition of the module, but the implementation may copy them into the
.asy and .cc to help explain the resulting code.</p>
<h3>Verbatim Code Blocks</h3>
<p>Verbatim code, ie. code to be copied directly into the either
the output .asy or .cc file can be specified in the .asyc file by
enclosing it in a verbatim code block. This starts with the special
identifier <code>verbatim</code> followed by either <code>c++</code>
or <code>asy</code> to specify into which file the code will be copied,
and then a block of code in braces. When the .asyc file is parsed,
the parser keeps track of matching open and close braces inside the
verbatim code block, so that the brace at the start of the block can
be matched with the one at the end. This matching process will ignore
braces occuring in comments and string and character literals.</p>
<h4>Open issue</h4>
<p>It may prove to be impractical to walk through the code, matching
braces. Also, this plan precludes having a verbatim block with an
unbalanced number of braces which might be useful, say to start a
namespace at the beginning of the C++ file, and end it at the end of the
file. As such, it may be useful to have another technique. A really
simple idea (with obvious drawbacks) would be to use the first closing
braces that occur at the same indentation level as the verbatim keyword
(assuming that the code block itself will be indented). Other
alternatives are to use more complicated tokens such as <code>%{</code>
and <code>%}</code>, or the shell style <code><<EOF</code>.</p>
<h3>Function Definitions</h3>
<p>A function definition given at the top level of the file (and not
inside a verbatim block) looks much like a function definition in
Asymptote or C++, but is actually a mix of both. The header of the
function is given in Asymptote code, and defines how the function will
look in the resulting Asymptote module. The body, on the other hand, is
given in C++, and defines how the function is implemented in C++. As a
simple example, consider:
<pre>real sum(real x, real y=0.0) {
return x+y;
}</pre>
<h4>Header</h4>
<p> The header of the definition gives
the name, permission, return type, and parameters of the function.
Because the function is defined for use in Asymptote, all of the types
are given as Asymptote types.</p>
<h5>Permissions</h5>
<p>As in pure Asymptote, the function can optionally be given a
<code>private</code>, <code>restricted</code> or <code>public</code>
permission. If not specified, the permission is <code>public</code> by
default. This is the permission that the function will have when it is
part of the Asymptote module. The example of <code>sum</code> above
specifies no permission, so it is public.</p>
<p>Just as public methods such as <code>plain.draw</code> can be
re-assigned by scripts that import the <code>plain</code> module, the
current plan is to allow Asymptote code to modify public members of any
module, including ones defined using native code. This is in contrast
to builtin functions bindings, which cannot be modified.</p>
<h5>Return Type</h5>
<p>This gives the Asymptote return type of the function. This cannot be
an arbitrary Asymptote type, but must one which maps to a C++ type as
explained in the type mapping section above. Our example of <code>sum</code> gives
<code>real</code> as a return type, which maps to the C++ type
<code>asy::real</code>.</p>
<h5>Function Name</h5>
<p>This gives the name of the function as it will appear in the
Asymptote module. In our example, the Asymptote name is
<code>sum</code>. The name can be any Asymptote identifier, including
operator names, such as <code>operator +</code>.</p>
<p>It is important to note that the Asymptote name has no relation to
the C++ name of the function, which may be something strange, such as
<code>_asy_func_modulename162</code>. Also, the actual signature and
return type of the C++ function may bear no relation to the Asymptote
signature. That said, the C++ name of the function may be defined by
giving the function name as <code>asyname:cname</code>. Then it can be
referred to by other C++ code. The function will be defined with C
calling convention, so that its name is not mangled.</p>
<h5>Formal Parameters</h5>
<p>The function header takes a list of formal parameters. Just as in
pure Asymptote code, these can include <code>explicit</code>
keywords, type declarations with array and functional types, and rest
parameters. Just as with the return type of the function, the type of
each of the parameters must map to a C++ type.</p>
<p>Parameters may be given an optional Asymptote name and an optional
C++ name. These may be declared in one of six ways as in the following
examples:</p>
<pre>void f(int)
void f(int name)
void f(int :)
void f(int asyname:)
void f(int :cname)
void f(int asyname:cname)
</pre>
<p>If the parameter just contains a type, with no identifier,
then it has no Asymptote name and no C++ name. If it contains a single
name (with no colon), then that name is both the Asymptote and the C++
name. If it contains a colon in the place of an identifier, with an
optional name in front of the colon and an optional name behind the
colon, than the name in front (if given) is the Asymptote name, and the
name behind (if given) is the C++ name.</p>
<p>The Asymptote name can be any Asymptote identifier, including
operator names, but the C++ name must be a valid C++ identifier. For
instance <code>void f(int operator +)</code> is not allowed, as the
parameter would not have a valid C++ name. The examples
<code>void f(int operator +:)</code> and
<code>void f(int operator +:addop)</code> are allowed.</p>
<p>When called by Asymptote code, named arguments are only matched to
the Asymptote names, so for example a function defined by
<code>void f(int :x, string x:y)</code> could be called by
<code>f(x="hi mom", 4)</code>, but one defined by
<code>void f(int x, string x:y)</code> could not.</p>
<p>Each formal parameter may take a piece of code as a default value.
Because the function is implemented in C++, this code must be given as
C++ code. More akin to Asymptote than C++, default arguments may occur
for any non-rest parameters, not just those at the end of the list, and
may refer to earlier parameters in the list. Earlier parameters are
refered to by their C++ names. Example:
<pre>void drawbox(pair center, real width, real height=2*width, pen p)</pre>
Default arguments are parsed by finding the next comma that is not part
of a comment, string literal, or character constant, and is not nested
inside parentheses. The C++ code between the equals-sign and the comma
is taken as the expression for the default argument.</p>
<h4>Body</h4>
<p>The body of the function is written as C++ code. When the .asyc
file is processed, this C++ code is copied verbatim into an actual C++
function providing the implementation. However, the actual body of the
resultant C++ function may contain code other than the body provided by
the user. This auxillary code could include instruction to retrieve the
arguments of the function from their representation in the Asymptote
virtual machine and bind them to local variables with their C++ names.
It could also include initialization and finalization code for the
function.</p>
<p>In writing code for the function body, one can be assured that all
function arguments with C++ names have been bound and are therefore
usable in the code. Since all parameters must have Asymptote types that
map to C++ types, the types of the paramaters in the body have the type
resulting from that mapping.</p>
<p>The <code>return</code> keyword can be used to return the result of
the function (or without an expression, if the return type was declared
as void). The Asymptote return type must map to a C++ type, and the
expression given in the return statement will be implicitly cast to that
type.</p>
<p>Since the implementation will likely not use an actual return
statement to return the value of the function back to the Asymptote
virtual machine, the interpreter of the .asyc file may walk through the
code converting return expressions into a special format in the actual
implementation of the function.<p>
<h3>Opaque Type Declarations</h3>
<p>There are a number of mappings between Asymptote and C++ types
builtin to the facility. For instance <code>int</code> maps to
<code>asy::Int</code> and <code>real</code> to <code>asy::real</code>.
Users, however, may want to reference other C++ objects in Asymptote
code. This done though opaque type declarations.</p>
<p>An opaque type declaration is given by an optional permission
modifier, the keyword <code>asytype</code>, a C++ type, and an Asymptote
identifier; in that order.</p>
<pre>
// Examples
asytype char char;
public asytype const std::list<asy::Int> *intList;
private asytype const struct dirert *entry_t;
</pre>
<p>This declaration mapping the Asymptote identifier to the C++ type
within the module. The permission of the Asymptote type is given by the
permission modifier (or public if the modifier is omitted). The type is
opaque, in that none of its internal structure is revealed in the
Asymptote code. Like any other type, however, objects of this new type
can be returned from functions, given as an arguments to functions, and
stored in variables, structures and arrays.</p>
<p>In many cases, such as the directory listing example at the start, it
will be practical to declare the type as private, and use an Asymptote
structure as a wrapper hiding the C++ implementation.</p>
</body>
</html>
|