/* xlsclient.c -- DDE command line client for XLISP-STAT. */
/* Copyright (c) 1999, by Luke Tierney. */
/* You may give out copies of this software; for conditions see the file */
/* COPYING included with this distribution. */
/* The thread-based IO design is adapted from Rterm and Tcl. */
#include <windows.h>
#include <ddeml.h>
#include <stdio.h>
#include <setjmp.h>
#define ErrorPrint(str) fprintf(stderr, "Error: %s\n", str)
#define FatalError(str) do { ErrorPrint(str); exit(1); } while (0)
#define SERVICE_NAME "XLISP-STAT"
#define TOPIC_NAME "CMDLINE"
#define BUFFER_SIZE 10000
char buf[BUFFER_SIZE];
char line[BUFFER_SIZE];
size_t bufpos;
int mych;
HANDLE ready_for_input = NULL;
HANDLE input_available = NULL;
BOOL interrupted = FALSE;
BOOL executing = FALSE;
BOOL done = FALSE;
DWORD mainThreadID;
jmp_buf read_reset;
#define RESET_INTERRUPT 1
#define RESET_OVERFLOW 2
int mygetch(void)
{
int c;
static size_t linesize = 0;
static size_t linepos = 0;
static BOOL inited = FALSE;
MSG msg;
if (done) return EOF;
if (linepos >= linesize) {
if (! inited) {
SetEvent(ready_for_input);
inited = TRUE;
}
while (GetMessage(&msg, NULL, 0, 0)) {
if (WaitForSingleObject(input_available, 0) == WAIT_OBJECT_0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (interrupted) {
interrupted = FALSE;
longjmp(read_reset, RESET_INTERRUPT);
}
else {
linesize = strlen(line);
linepos = 0;
SetEvent(ready_for_input);
}
}
return line[linepos++];
}
void buffer_putc(int c)
{
if (bufpos < BUFFER_SIZE)
buf[bufpos++] = c;
else longjmp(read_reset, RESET_OVERFLOW);
}
BOOL eat_single_comment(void)
{
int c;
while (TRUE) {
c = mygetch();
if (c == EOF) return FALSE;
buffer_putc(c);
if (c == '\n') return TRUE;
}
}
BOOL eat_multi_comment(void)
{
int c;
while (TRUE) {
c = mygetch();
if (c == EOF) return FALSE;
buffer_putc(c);
switch (c) {
case '|':
c = mygetch();
if (c == EOF) return FALSE;
buffer_putc(c);
if (c == '#') return TRUE;
break;
case '#':
c = mygetch();
if (c == EOF) return FALSE;
buffer_putc(c);
if (c == '|' && ! eat_multi_comment()) return FALSE;
break;
}
}
}
BOOL read_expression(void)
{
int c, parcount;
switch (setjmp(read_reset)) { /**** is this legit? */
case RESET_INTERRUPT: ErrorPrint("user interrupt"); break;
case RESET_OVERFLOW: ErrorPrint("buffer overflow"); break;
}
bufpos = 0;
parcount = 0;
while (TRUE) {
c = mygetch();
if (c == EOF) goto eof;
buffer_putc(c);
switch (c) {
case '\n': if (parcount <= 0) goto complete; break;
case '(': parcount++; break;
case ')': parcount--; if (parcount <= 0) goto complete; break;
case ';': if (! eat_single_comment()) goto eof; break;
case '#':
c = mygetch();
if (c == EOF) goto eof;
buffer_putc(c);
switch (c) {
case '(': parcount++; break;
case '|': if (! eat_multi_comment()) goto eof; break;
case '\\':
c = mygetch();
if (c == EOF) goto eof;
buffer_putc(c);
break;
}
break;
}
}
complete:
buffer_putc('\0');
return TRUE;
eof:
buffer_putc('\0');
return FALSE;
}
BOOL WINAPI signal_handler(DWORD type)
{
switch (type) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
if (executing)
fprintf(stderr,
"**** XLISP-STAT is executing a command.\n"
"**** To interrupt it, do a BREAK in the XLISP-STAT window\n");
else {
interrupted = TRUE;
SetEvent(input_available);
PostThreadMessage(mainThreadID, WM_NULL, 0, 0);
}
break;
}
return TRUE;
}
DWORD WINAPI readline(LPVOID dummy)
#pragma argsused
{
do {
WaitForSingleObject(ready_for_input, INFINITE);
done = fgets(line, sizeof(line), stdin) == NULL;
SetEvent(input_available);
PostThreadMessage(mainThreadID, WM_NULL, 0, 0);
} while (! done);
return FALSE;
}
HDDEDATA CALLBACK DdeProc(UINT type, UINT fmt, HCONV hconv,
HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
DWORD dw1, DWORD dw2)
#pragma argsused
{
return (HDDEDATA) 0;
}
/**** I can only tell if I am executing if I do the parsing here. */
HDDEDATA DdeExecute(char *buf, HCONV hconv, DWORD timeout)
{
HDDEDATA hdata;
executing = TRUE;
hdata = DdeClientTransaction((LPVOID) (buf), strlen(buf) + 1, hconv, NULL,
CF_TEXT, XTYP_EXECUTE, timeout, NULL);
executing = FALSE;
return hdata;
}
void DdeRequestAndWrite(HCONV hconv, HSZ value, DWORD timeout)
{
HDDEDATA hdata;
hdata = DdeClientTransaction(NULL, 0, hconv, value, CF_TEXT,
XTYP_REQUEST, timeout, NULL);
if (hdata != NULL) {
DWORD size;
char *p = (char *) DdeAccessData(hdata, &size);
if (p != NULL && memchr(p, 0, size) != NULL) {
fputs(p, stdout);
fflush(stdout);
}
else ErrorPrint("bad value string");
DdeUnaccessData(hdata);
DdeFreeDataHandle(hdata);
}
else ErrorPrint("value request failed");
}
int main(int argc, char *argv[])
{
DWORD ddeInst = 0;
HSZ service, topic, value;
char *servicename;
char *topicname;
DWORD read_thread_id;
HANDLE read_thread = NULL;
HSZ hszbreak;
HCONV hconv;
DWORD timeout = 60000L;
mainThreadID = GetCurrentThreadId();
/* get the service and topic names */
switch (argc) {
case 1: topicname = TOPIC_NAME; servicename = SERVICE_NAME; break;
case 2: topicname = TOPIC_NAME; servicename = argv[1]; break;
case 3: servicename = argv[1]; topicname = argv[2]; break;
default: fprintf(stderr, "usage: %s [service [topic]]\n", argv[0]); exit(1);
}
/* initialize DDEML */
switch (DdeInitialize(&ddeInst,(PFNCALLBACK)DdeProc,APPCMD_CLIENTONLY,0)) {
case DMLERR_NO_ERROR: break;
case DMLERR_DLL_USAGE: FatalError("DLL usage");
case DMLERR_INVALIDPARAMETER: FatalError("invalid parameter");
case DMLERR_SYS_ERROR: FatalError("invalid parameter");
default: FatalError("unknown error");
}
if (ddeInst == 0) FatalError("DDE initialization failed");
/* initialize the synchronization events and start the reader thread */
if (SetConsoleCtrlHandler(signal_handler, TRUE) == FALSE ||
(ready_for_input = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL ||
(input_available = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL ||
(read_thread = CreateThread(NULL, 0, readline, NULL,
0, &read_thread_id)) == NULL)
goto cleanup;
/* connect to the server */
service = DdeCreateStringHandle(ddeInst, servicename, CP_WINANSI);
topic = DdeCreateStringHandle(ddeInst, topicname, CP_WINANSI);
hconv = DdeConnect(ddeInst, service, topic, NULL);
if (service != NULL) DdeFreeStringHandle(ddeInst, service);
if (topic != NULL) DdeFreeStringHandle(ddeInst, topic);
if (hconv == NULL) FatalError("can't connect to the XLISP-STAT DDE server");
/* initialize the string handles for the value and break request */
value = DdeCreateStringHandle(ddeInst, "VALUE", CP_WINANSI);
hszbreak = DdeCreateStringHandle(ddeInst, "BREAK", CP_WINANSI);
/* process standard input until it is empty */
DdeRequestAndWrite(hconv, value, timeout);
while (read_expression()) {
if (DdeExecute(buf, hconv, timeout))
DdeRequestAndWrite(hconv, value, timeout);
else ErrorPrint("execute failed");
}
cleanup:
/* clean up the reader thread and synchronization events */
if (read_thread != NULL) CloseHandle(read_thread);
if (ready_for_input != NULL) CloseHandle(ready_for_input);
if (input_available != NULL) CloseHandle(input_available);
/* close the connection and drop DDEML */
DdeDisconnect(hconv);
if (value != NULL) DdeFreeStringHandle(ddeInst, value);
if (hszbreak != NULL) DdeFreeStringHandle(ddeInst, hszbreak);
DdeUninitialize(ddeInst);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1