75e0f96073cefaab0c072a58eefd8abf2719946e
3 * $Id: rxglue.c,v 1.4 2002/02/02 22:43:50 mdw Exp $
5 * REXX glue for C core functionality
7 * (c) 2001 Mark Wooding
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of Jog: Programming for a jogging machine.
14 * Jog is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * Jog is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Jog; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 /*----- Revision history --------------------------------------------------*
32 * Revision 1.4 2002/02/02 22:43:50 mdw
33 * Provide a decent interface for finding out about the audio cache and
34 * configuring its capacity.
36 * Revision 1.3 2002/02/02 19:17:41 mdw
37 * New audio subsystem.
39 * Revision 1.2 2002/01/30 09:22:48 mdw
40 * Use memory-allocation functions provided by the REXX interpreter.
41 * Now that configuration can be applied after initialization, allow
42 * @txconf@ to set parameters. Make @txsend@ add a newline to its output,
45 * Revision 1.1 2002/01/25 19:34:45 mdw
50 /*----- Header files ------------------------------------------------------*/
65 #include <sys/types.h>
70 #define RX_STRONGTYPING
73 #include <mLib/alloc.h>
75 #include <mLib/dstr.h>
83 /*----- Static variables --------------------------------------------------*/
85 static txport
*tx
= 0;
87 /*----- Memory allocation functions ---------------------------------------*/
89 static void *rx_alloc(size_t sz
)
91 void *p
= RexxAllocateMemory(sz
);
97 static void rx_free(void *p
)
102 /*----- Conversion functions ----------------------------------------------*/
104 /* --- @rxs_putm@ --- *
106 * Arguments: @RXSTRING *x@ = pointer to REXX string structure
108 * @const void *p@ = pointer to data block
109 * @size_t sz@ = size of data
111 * @const dstr *d@ = pointer to source string
112 * For @rxs_putf@ and @rxs_vputf@:
113 * @const char *m@ = message format string
117 * Use: Stashes some text in an @RXSTRING@, overwriting whatever was
118 * there before. We assume that the previous contents don't
122 #define RXS_PUTM(x, p, sz) do { \
123 RXSTRING *_x = (x); \
124 const void *_p = (p); \
126 if (!_x->strptr || _x->strlength < _sz) \
127 _x->strptr = rx_alloc(_sz); \
128 memcpy(_x->strptr, _p, _sz); \
129 _x->strlength = _sz; \
132 static void rxs_putm(RXSTRING
*x
, const void *p
, size_t sz
)
137 #define RXS_PUTD(x, d) do { \
139 RXS_PUTM((x), _d->buf, _d->len); \
142 static void rxs_putd(RXSTRING
*x
, dstr
*d
) { RXS_PUTD(x
, d
); }
144 static void rxs_vputf(RXSTRING
*x
, const char *m
, va_list *ap
)
147 dstr_vputf(&d
, m
, ap
);
152 static void rxs_putf(RXSTRING
*x
, const char *m
, ...)
157 dstr_vputf(&d
, m
, &ap
);
163 /* --- @rxs_get@ --- *
165 * Arguments: @const RXSTRING *x@ = pointer to a REXX string
166 * @dstr *d@ = where to put it
170 * Use: Pulls a REXX string out and puts it in a dynamic string.
173 #define RXS_GET(x, d) do { \
174 const RXSTRING *_x = (x); \
176 DPUTM(_dd, _x->strptr, _x->strlength); \
180 static void rxs_get(const RXSTRING
*x
, dstr
*d
) { RXS_GET(x
, d
); }
182 /* --- @rxs_tol@ --- *
184 * Arguments: @const RXSTRING *x@ = pointer to a REXX string
185 * @long *ii@ = where to put the answer
187 * Returns: Zero on success, or nonzero on error.
189 * Use: Fetches an integer from a REXX string. This doesn't cope
190 * with multiprecision integers or similar silliness.
193 static int rxs_tol(const RXSTRING
*x
, long *ii
)
196 const char *p
= x
->strptr
, *l
= p
+ x
->strlength
;
202 #define MINR (LONG_MIN/10)
203 #define MIND (LONG_MIN%10)
205 while (p
< l
&& isspace((unsigned char)*p
))
211 else if (*p
== '-') {
215 while (p
< l
&& isspace((unsigned char)*p
))
217 while (p
< l
&& isdigit((unsigned char)*p
)) {
219 if (i
< MINR
|| (i
== MINR
&& -j
< MIND
))
224 while (p
< l
&& isspace((unsigned char)*p
))
226 if (p
< l
|| !(f
& f_ok
))
243 /* --- @rxs_block@ --- *
245 * Arguments: @const RXSTRING *x@ = a REXX string
246 * @unsigned long *t@ = where to put the block spec
248 * Returns: Zero if OK, nonzero on error.
250 * Use: Picks out a blockingness spec.
253 static int rxs_block(const RXSTRING
*x
, unsigned long *t
)
257 if (!x
->strptr
|| x
->strlength
< 1)
259 switch (x
->strptr
[0]) {
265 if (rxs_tol(x
, &i
) || i
< 0)
273 /*----- REXX functions ----------------------------------------------------*/
275 static APIRET APIENTRY
rxfn_test(const char *fn
, ULONG ac
, RXSTRING
*av
,
276 const char *sn
, RXSTRING
*r
)
280 printf("test entry\n"
282 for (i
= 0; i
< ac
; i
++) {
285 printf(" av[%lu] = `", i
);
286 fwrite(av
[i
].strptr
, 1, av
[i
].strlength
, stdout
);
287 if (rxs_tol(&av
[i
], &l
))
290 printf("' (%ld)\n", l
);
292 printf("tx = `%s'; f = `%s'; c = `%s'.\n", txname
, txfile
, txconf
);
293 rxs_putf(r
, "function `%s' completed ok", fn
);
297 /* --- @txname()@ ---
301 * Returns: The currently-selected transport name.
304 static APIRET APIENTRY
rxfn_txname(const char *fn
, ULONG ac
, RXSTRING
*av
,
305 const char *sn
, RXSTRING
*r
)
309 rxs_putf(r
, "%s", txname
);
313 /* --- @txfile()@ ---
317 * Returns: The currently-selected transport filename.
320 static APIRET APIENTRY
rxfn_txfile(const char *fn
, ULONG ac
, RXSTRING
*av
,
321 const char *sn
, RXSTRING
*r
)
325 rxs_putf(r
, "%s", txfile ? txfile
: "");
329 /* --- @txconf([CONFIG])@ ---
331 * Arguments: @CONFIG@ = optional string to set
333 * Returns: The currently-selected transport configuration string.
336 static APIRET APIENTRY
rxfn_txconf(const char *fn
, ULONG ac
, RXSTRING
*av
,
337 const char *sn
, RXSTRING
*r
)
341 if (ac
> 0 && av
[0].strptr
) {
347 rc
= tx_configure(tx
, d
.buf
);
352 rxs_putf(r
, "%s", txconf ? txconf
: "");
356 /* --- @txinit([NAME], [FILE], [CONFIG])@ ---
358 * Arguments: @NAME@ = transport name to select
359 * @FILE@ = transport filename
360 * @CONFIG@ = transport configuration string
364 * Use: Initializes a transport using the given settings. Omitted
365 * arguments are filled in from the command line, or internal
369 static APIRET APIENTRY
rxfn_txinit(const char *fn
, ULONG ac
, RXSTRING
*av
,
370 const char *sn
, RXSTRING
*r
)
372 const char *n
= txname
, *f
= txfile
, *c
= txconf
;
373 dstr dn
= DSTR_INIT
, df
= DSTR_INIT
, dc
= DSTR_INIT
;
379 if (ac
>= 1 && av
[0].strptr
) {
380 rxs_get(&av
[0], &dn
);
383 if (ac
>= 2 && av
[1].strptr
) {
384 rxs_get(&av
[1], &df
);
387 if (ac
>= 3 && av
[2].strptr
) {
388 rxs_get(&av
[2], &dc
);
391 tx
= tx_create(n
, f
, c
);
400 /* --- @txsend(STRING, [OPTION])@ --- *
402 * Arguments: @STRING@ = string to send
403 * @OPTION@ = `l' or `n' (for `linebreak' or `nolinebreak')
407 * Use: Sends a string (exactly as written) to the transport.
410 static APIRET APIENTRY
rxfn_txsend(const char *fn
, ULONG ac
, RXSTRING
*av
,
411 const char *sn
, RXSTRING
*r
)
413 if ((ac
!= 1 && ac
!= 2) || !tx
|| !av
[0].strptr
)
415 tx_write(tx
, av
[0].strptr
, av
[0].strlength
);
416 if (ac
== 1 || !av
[1].strptr
|| !av
[1].strlength
||
417 av
[1].strptr
[0] == 'l' || av
[1].strptr
[0] == 'L')
422 /* --- @txrecv([MILLIS])@ --- *
424 * Arguments: @MILLIS@ = how long (in milliseconds) to wait, or `forever'
426 * Returns: The string read (may be null if nothing available -- sorry).
428 * Use: Reads the next line from the transport. If @MILLIS@ is an
429 * integer, then give up after that many milliseconds of
430 * waiting; if it is `forever' (or anything beginning with an
431 * `f') then don't give up. The default is to wait forever.
434 static APIRET APIENTRY
rxfn_txrecv(const char *fn
, ULONG ac
, RXSTRING
*av
,
435 const char *sn
, RXSTRING
*r
)
438 unsigned long t
= FOREVER
;
442 if (ac
>= 1 && rxs_block(&av
[0], &t
))
449 rxs_putm(r
, l
->s
, l
->len
);
455 /* --- @TXEOF()@ --- *
459 * Returns: True if end-of-file has been seen on the transport, otherwise
463 static APIRET APIENTRY
rxfn_txeof(const char *fn
, ULONG ac
, RXSTRING
*av
,
464 const char *sn
, RXSTRING
*r
)
468 rxs_putf(r
, "%d", tx
->s
== TX_CLOSED
&& !tx
->ll
);
472 /* --- @txready([MILLIS])@ --- *
474 * Arguments: @MILLIS@ = how long (in milliseconds) to wait, or `forever'
476 * Returns: True if a line is ready, otherwise false.
478 * Use: Returns whether the transport is ready for reading. If
479 * @MILLIS@ is an integer, then wait for at most that many
480 * milliseconds before returning. If @MILLIS@ is `forever' (or
481 * anything beginning with `f') then wait forever for
482 * readiness. This isn't useless: it can trip the end-of-file
483 * detector. If @MILLIS@ is omitted, return immediately (as if
484 * 0 had been specified).
487 static APIRET APIENTRY
rxfn_txready(const char *fn
, ULONG ac
, RXSTRING
*av
,
488 const char *sn
, RXSTRING
*r
)
494 if (ac
>= 1 && rxs_block(&av
[0], &t
))
496 rxs_putf(r
, "%d", !!tx_read(tx
, t
));
500 /* --- @AUPLAY(TAG, [FLAG])@ --- *
502 * Arguments: @TAG@ = audio sample tag to play
503 * @FLAG@ = a string to explain what to do more clearly.
505 * Returns: True if it succeeded.
507 * Use: Plays a sample. If @FLAG@ begins with `t', don't report
508 * errors if the sample can't be found.
511 static APIRET APIENTRY
rxfn_auplay(const char *fn
, ULONG ac
, RXSTRING
*av
,
512 const char *sn
, RXSTRING
*r
)
517 if (ac
< 1 || !av
[0].strlength
|| ac
> 2)
520 if (ac
> 1 && av
[1].strlength
>= 1 &&
521 (av
[1].strptr
[0] == 't' || av
[1].strptr
[0] == 'T'))
522 rc
= au_tryplay(d
.buf
);
526 rxs_putf(r
, "%d", rc
);
530 /* --- @AUFETCH(TAG)@ --- *
532 * Arguments: @TAG@ = audio sample tag to play
534 * Returns: True if it succeeded.
536 * Use: Prefetches a sample into the cache.
539 static APIRET APIENTRY
rxfn_aufetch(const char *fn
, ULONG ac
, RXSTRING
*av
,
540 const char *sn
, RXSTRING
*r
)
547 if (ac
< 1 || !av
[0].strlength
|| ac
> 1)
550 if ((s
= au_find(d
.buf
)) != 0 &&
551 (a
= au_fetch(s
)) != 0) {
556 rxs_putf(r
, "%d", rc
);
560 /* --- @AUNUM(TAG)@ --- *
562 * Arguments: @NUM@ = a number to be read
566 * Use: Reads a number aloud to the audio device.
569 static APIRET APIENTRY
rxfn_aunum(const char *fn
, ULONG ac
, RXSTRING
*av
,
570 const char *sn
, RXSTRING
*r
)
574 if (ac
< 1 || !av
[0].strlength
|| ac
> 1)
582 /* --- @AUCACHE([FLAG], [VALUE, ...]@ --- *
584 * Arguments: @FLAG@ = operation to perform
586 * Returns: Dependent on operation.
588 * Use: If @FLAG@ is omitted or `Info', returns audio cache usage
589 * information as words in the following order:
591 * sz_max Maximum allowed cache size
592 * sz_total Total size used by samples
593 * sz_spare Size used by `spare' samples
594 * sz_queue Size used by queued samples
595 * n_total Total number of cached samples
596 * n_spare Number of `spare' samples
597 * n_queue Number of queued samples
598 * hits Number of cache hits
599 * misses Number of cache misses
601 * If @FLAG@ is `Max', sets the maximum cache size to the first
602 * @VALUE@ (if set), and returns the old maximum on its own.
604 * If @FLAG@ is `Usage', returns the `sz_*' items, as a list of
607 * If @FLAGS@ is `Numbers', returns the `n_*' items, as a list
610 * If @FLAGS@ is `Hits', returns `hits' and `misses' as a pair
614 static APIRET APIENTRY
rxfn_aucache(const char *fn
, ULONG ac
, RXSTRING
*av
,
615 const char *sn
, RXSTRING
*r
)
621 if (ac
< 1 || !av
[0].strlength
)
623 switch (av
[0].strptr
[0]) {
624 case 'i': case 'I': info
:
625 rxs_putf(r
, "%lu %lu %lu %lu %u %u %u %lu %lu",
626 (unsigned long)c
.sz_max
, (unsigned long)c
.sz_total
,
627 (unsigned long)c
.sz_spare
, (unsigned long)c
.sz_queue
,
628 c
.n_total
, c
.n_spare
, c
.n_total
, c
.hits
, c
.misses
);
633 if (rxs_tol(&av
[i
], &max
))
635 au_setcachelimit(max
);
638 rxs_putf(r
, "%lu", (unsigned long)c
.sz_max
);
641 rxs_putf(r
, "%lu %lu %lu %lu",
642 (unsigned long)c
.sz_max
, (unsigned long)c
.sz_total
,
643 (unsigned long)c
.sz_spare
, (unsigned long)c
.sz_queue
);
646 rxs_putf(r
, "%u %u %u", c
.n_total
, c
.n_spare
, c
.n_total
);
649 rxs_putf(r
, "%lu %lu", c
.hits
, c
.misses
);
659 /* --- @MILLIWAIT(MILLIS)@ --- *
661 * Arguments: @MILLIS@ = how long (in milliseconds) to wait
665 * Use: Waits for @MILLIS@ milliseconds. Always.
668 static APIRET APIENTRY
rxfn_milliwait(const char *fn
, ULONG ac
, RXSTRING
*av
,
669 const char *sn
, RXSTRING
*r
)
674 if (ac
!= 1 || !av
[0].strptr
)
676 if (rxs_tol(&av
[0], &l
) || l
< 0)
678 tv
.tv_sec
= l
/ 1000;
679 tv
.tv_usec
= (l
% 1000) * 1000;
680 select(0, 0, 0, 0, &tv
);
684 /*----- Initialization ----------------------------------------------------*/
686 struct rxfntab
{ char *name
; RexxFunctionHandler
*fn
; };
688 static const struct rxfntab rxfntab
[] = {
689 { "test", rxfn_test
},
690 { "txname", rxfn_txname
},
691 { "txfile", rxfn_txfile
},
692 { "txconf", rxfn_txconf
},
693 { "txinit", rxfn_txinit
},
694 { "txsend", rxfn_txsend
},
695 { "txrecv", rxfn_txrecv
},
696 { "txeof", rxfn_txeof
},
697 { "txready", rxfn_txready
},
698 { "auplay", rxfn_auplay
},
699 { "aufetch", rxfn_aufetch
},
700 { "aucache", rxfn_aucache
},
701 { "aunum", rxfn_aunum
},
702 { "milliwait", rxfn_milliwait
},
706 /* --- @rx_init@ --- *
712 * Use: Initializes the REXX external functions.
717 const struct rxfntab
*f
;
720 for (f
= rxfntab
; f
->fn
; f
++) {
721 if ((rc
= RexxRegisterFunctionExe(f
->name
, f
->fn
)) != 0) {
722 err_report(ERR_RXGLUE
, ERRRX_INIT
, rc
,
723 "couldn't register function `%s' (code %d)", f
->name
, rc
);
729 /*----- Running REXX programs ---------------------------------------------*/
731 /* --- @rx_run@ --- *
733 * Arguments: @const char *name@ = pointer to filename (or null)
734 * @const void *p@ = pointer to program text
735 * @size_t sz@ = size of program text
736 * @int ac@ = number of arguments
737 * @const char *const *av@ = vector of command-line arguments
739 * Returns: Exit code from program.
741 * Use: Runs a REXX script from memory.
744 int rx_run(const char *name
, const void *p
, size_t sz
,
745 int ac
, const char *const *av
)
755 /* --- Set things up --- */
759 MAKERXSTRING(prog
[0], (void *)p
, sz
);
760 MAKERXSTRING(prog
[1], 0, 0);
761 argv
= rx_alloc(ac
* sizeof(*argv
));
762 for (i
= 0; i
< ac
; i
++)
763 MAKERXSTRING(argv
[i
], (char *)av
[i
], strlen(av
[i
]));
765 /* --- Run the script --- */
767 MAKERXSTRING(res
, 0, 0);
768 rc
= RexxStart(ac
, argv
, name
, prog
,
769 "SYSTEM", RXSUBROUTINE
, 0, &badrc
, &res
);
771 rx_free(RXSTRPTR(res
));
774 err_report(ERR_RXERR
, 0, -rc
, "rexx error from script `%s'", name
);
776 err_report(ERR_RXGLUE
, ERRRX_INTERP
, rc
, "intepreter internal error");
780 /* --- Pick apart the results --- */
782 dstr_putm(&d
, RXSTRPTR(res
), RXSTRLEN(res
));
783 rx_free(RXSTRPTR(res
));
791 /* --- @rx_runfile@ --- *
793 * Arguments: @const char *name@ = pointer to filename
794 * @int ac@ = number of command-line arguments
795 * @const char *const *av@ = vector of command-line arguments
797 * Returns: Exit code from program.
799 * Use: Runs a REXX script from a file, given its name.
802 int rx_runfile(const char *name
, int ac
, const char *const *av
)
810 /* --- Read the file into memory --- *
812 * This way avoids any crapness in the REXX implementation and means we can
813 * report errors in a more sensible way.
816 if ((fp
= fopen(name
, "r")) == 0)
819 n
= fread(buf
, 1, sizeof(buf
), fp
);
821 } while (n
== sizeof(buf
));
826 /* --- Now do the from-memory thing --- */
828 rc
= rx_run(name
, d
.buf
, d
.len
, ac
, av
);
832 /* --- Tidy up on errors --- */
838 err_report(ERR_RXGLUE
, ERRRX_SCRIPTREAD
, errno
,
839 "couldn't read script `%s': %s", name
, strerror(errno
));
843 /*----- That's all, folks -------------------------------------------------*/