blob: ec178ff448190dcf7cb6579d44815709ab3f1f11 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2005 The Regents of the University of California.
* This material was produced under U.S. Government contract W-7405-ENG-36
* for Los Alamos National Laboratory, which is operated by the University
* of California for the U.S. Department of Energy. The U.S. Government has
* rights to use, reproduce, and distribute this software. NEITHER THE
* GOVERNMENT NOR THE UNIVERSITY MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR
* ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified
* to produce derivative works, such modified software should be clearly
* marked, so as not to confuse it with the version available from LANL.
*
* Additionally, this program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* LA-CC 04-115
******************************************************************************/
/*
* Based on the QNX Java implementation of the MI interface
*/
/**
<pre>
`OUTPUT :'
`( OUT-OF-BAND-RECORD )* [ RESULT-RECORD ] "(gdb)" NL'
`RESULT-RECORD :'
` [ TOKEN ] "^" RESULT-CLASS ( "," RESULT )* NL'
`OUT-OF-BAND-RECORD :'
`ASYNC-RECORD | STREAM-RECORD'
`ASYNC-RECORD :'
`EXEC-ASYNC-OUTPUT | STATUS-ASYNC-OUTPUT | NOTIFY-ASYNC-OUTPUT'
`EXEC-ASYNC-OUTPUT :'
`[ TOKEN ] "*" ASYNC-OUTPUT'
`STATUS-ASYNC-OUTPUT :'
`[ TOKEN ] "+" ASYNC-OUTPUT'
`NOTIFY-ASYNC-OUTPUT :'
`[ TOKEN ] "=" ASYNC-OUTPUT'
`ASYNC-OUTPUT :'
`ASYNC-CLASS ( "," RESULT )* NL'
`RESULT-CLASS :'
`"done" | "running" | "connected" | "error" | "exit"'
`ASYNC-CLASS :'
`"stopped" | OTHERS' (where OTHERS will be added depending on the
needs--this is still in development).
`RESULT :'
` VARIABLE "=" VALUE'
`VARIABLE :'
` STRING '
`VALUE :'
` CONST | TUPLE | LIST '
`CONST :'
`C-STRING'
`TUPLE :'
` "{}" | "{" RESULT ( "," RESULT )* "}" '
`LIST :'
` "[]" | "[" VALUE ( "," VALUE )* "]" | "[" RESULT ( "," RESULT )*
"]" '
`STREAM-RECORD :'
`CONSOLE-STREAM-OUTPUT | TARGET-STREAM-OUTPUT | LOG-STREAM-OUTPUT'
`CONSOLE-STREAM-OUTPUT :'
`"~" C-STRING'
`TARGET-STREAM-OUTPUT :'
`"@" C-STRING'
`LOG-STREAM-OUTPUT :'
`"&" C-STRING'
`NL :'
`CR | CR-LF'
`TOKEN :'
_any sequence of digits_.
`C-STRING :'
`""" SEVEN-BIT-ISO-C-STRING-CONTENT """'
</pre>
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "MIList.h"
#include "MIOOBRecord.h"
#include "MIValue.h"
#include "MIResult.h"
#include "MIResultRecord.h"
#include "MIOutput.h"
static MIResultRecord *processMIResultRecord(char *buffer, int id);
static MIOOBRecord *processMIOOBRecord(char *buffer, int id);
static MIList *processMIResults(char **buffer);
static MIResult *processMIResult(char **buffer);
static MIValue *processMIValue(char **buffer);
static MIValue *processMITuple(char **buffer);
static MIValue *processMIList(char **buffer);
static char *translateCString(char **buffer);
char *primaryPrompt = "(gdb)"; //$NON-NLS-1$
char *secondaryPrompt = ">"; //$NON-NLS-1$
/**
* Point of entry to create an AST for MI.
*
* @param buffer Output from MI Channel.
* @param mi MIOutput
* @see MIOutput
*/
void
MIParse(char *buffer, MIOutput *mi)
{
int id = -1;
char *s;
char *token;
while (buffer != NULL && *buffer != '\0') {
s = strchr(buffer, '\n');
if (s != NULL)
*s++ = '\0';
token = buffer;
buffer = s;
// Fetch the Token/Id
if (*token != '\0' && isdigit(*token)) {
id = strtol(token, &token, 10);
}
// ResultRecord ||| Out-Of-Band Records
if (*token != '\0') {
if (*token == '^') {
token++;
mi->rr = processMIResultRecord(token, id);
} else if (strncmp(token, primaryPrompt, strlen(primaryPrompt)) == 0) {
//break; // Do nothing.
} else {
MIOOBRecord *oob = processMIOOBRecord(token, id);
if (oob != NULL) {
if (mi->oobs == NULL) {
mi->oobs = MIListNew();
}
MIListAdd(mi->oobs, (void *)oob);
}
}
}
}
}
/**
* Assuming '^' was deleted from the Result Record.
*/
static MIResultRecord *
processMIResultRecord(char *buffer, int id)
{
MIResultRecord *rr = MIResultRecordNew();
rr->token = id;
if (strncmp(buffer, "done", 4) == 0) {
rr->resultClass = MIResultRecordDONE;
buffer += 4;
} else if (strncmp(buffer, "error", 5) == 0) {
rr->resultClass = MIResultRecordERROR;
buffer += 5;
} else if (strncmp(buffer, "exit", 4) == 0) {
rr->resultClass = MIResultRecordEXIT;
buffer += 4;
} else if (strncmp(buffer, "running", 7) == 0) {
rr->resultClass = MIResultRecordRUNNING;
buffer += 7;
} else if (strncmp(buffer, "connected", 9) == 0) {
rr->resultClass = MIResultRecordCONNECTED;
buffer += 9;
} else {
// FIXME:
// Error throw an exception?
}
// Results are separated by commas.
if (*buffer != '\0' && *buffer == ',') {
buffer++;
MIList *res = processMIResults(&buffer);
rr->results = res;
}
return rr;
}
/**
* Find OutOfBand Records depending on the starting token.
*/
static MIOOBRecord *
processMIOOBRecord(char *buffer, int id)
{
MIOOBRecord *oob = NULL;
char c = *buffer;
if (c == '*' || c == '+' || c == '=') {
// Consume the first char
buffer++;
switch (c) {
case '*' :
oob = NewMIExecAsyncOutput();
break;
case '+' :
oob = NewMIStatusAsyncOutput();
break;
case '=' :
oob = NewMINotifyAsyncOutput();
break;
}
oob->token = id;
// Extract the Async-Class
char *s = strchr(buffer, ',');
if (s != NULL) {
*s++ = '\0';
oob->class = strdup(buffer);
// Consume the async-class and the comma
buffer = s;
} else {
oob->class = strdup(buffer);
buffer += strlen(buffer);
}
MIList *res = processMIResults(&buffer);
oob->results = res;
} else if (c == '~' || c == '@' || c == '&') {
// Consume the first char
buffer++;
switch (c) {
case '~' :
oob = NewMIConsoleStreamOutput();
break;
case '@' :
oob = NewMITargetStreamOutput();
break;
case '&' :
oob = NewMILogStreamOutput();
break;
}
// translateCString() assumes that the leading " is deleted
if (*buffer != '\0' && *buffer == '"') {
buffer++;
}
oob->cstring = translateCString(&buffer);
} else {
// Badly format MI line, just pass it to the user as target stream
oob = NewMITargetStreamOutput();
oob->cstring = strdup(buffer); //$NON-NLS-1$
}
return oob;
}
/**
* Assuming that the usual leading comma was consumed.
* Extract the MI Result comma seperated responses.
*/
static MIList *
processMIResults(char **buffer)
{
MIList * aList = MIListNew();
MIResult * result = processMIResult(buffer);
if (result != NULL) {
MIListAdd(aList, (void *)result);
}
while (*(*buffer) != '\0' && *(*buffer) == ',') {
(*buffer)++;
result = processMIResult(buffer);
if (result != NULL) {
MIListAdd(aList, (void *)result);
}
}
return aList;
}
/**
* Construct the MIResult. Characters will be consume/delete
* moving forward constructing the AST.
*/
static MIResult *
processMIResult(char **buffer)
{
MIResult * result = MIResultNew();
MIValue * value;
char * equal;
if (*(*buffer) != '\0' && isalpha(*(*buffer)) && (equal = strchr(*buffer, '=')) != NULL) {
char *variable = *buffer;
*equal++ = '\0';
result->variable = strdup(variable); // TODO strdup?
*buffer = equal;
value = processMIValue(buffer);
result->value = value;
} else if(*(*buffer) != '\0' && *(*buffer) == '"') {
// This an error but we just swallow it and move on.
value = processMIValue(buffer);
result->value = value;
} else {
result->variable = strdup(*buffer);
result->value = NewMIConst(); // Empty string:???
*(*buffer) = '\0';
}
return result;
}
/**
* Find a MIValue implementation or return null.
*/
static MIValue *
processMIValue(char **buffer)
{
MIValue *value = NULL;
if (*(*buffer) != '\0') {
if (*(*buffer) == '{') {
(*buffer)++;
value = processMITuple(buffer);
} else if (*(*buffer) == '[') {
(*buffer)++;
value = processMIList(buffer);
} else if (*(*buffer) == '"') {
(*buffer)++;
value = NewMIConst();
value->cstring = translateCString(buffer);
}
}
return value;
}
/**
* Assuming the starting '{' was deleted form the StringBuffer,
* go to the closing '}' consuming/deleting all the characters.
* This is usually call by processMIvalue();
*/
static MIValue *
processMITuple(char **buffer)
{
MIValue *tuple = NewMITuple();
#ifdef __APPLE__
MIList *values = MIListNew();
MIValue *value;
MIResult *result;
#endif /* __APPLE__ */
MIList *results = NULL;
// Catch closing '}'
while (*(*buffer) != '\0' && *(*buffer) != '}') {
#ifdef __APPLE__
// Try for the MIValue first
value = processMIValue(buffer);
if (value != NULL) {
MIListAdd(values, (void *)value);
} else {
result = processMIResult(buffer);
if (result != NULL) {
if (results == NULL) {
results = MIListNew();
}
MIListAdd(results, (void *)result);
}
}
if (*(*buffer) != '\0' && *(*buffer) == ',') {
(*buffer)++;
}
#else /* __APPLE__ */
results = processMIResults(buffer);
#endif /* __APPLE__ */
}
if (*(*buffer) != '\0' && *(*buffer) == '}') {
(*buffer)++;
}
tuple->results = results;
#ifdef __APPLE__
tuple->values = values;
#endif /* __APPLE__ */
return tuple;
}
/**
* Assuming the leading '[' was deleted, find the closing
* ']' consuming/delete chars from the StringBuffer.
*/
static MIValue *
processMIList(char **buffer)
{
MIValue *list = NewMIList();
MIList *valueList = MIListNew();
MIList *resultList = MIListNew();
MIValue *value;
MIResult *result;
// catch closing ']'
while (*(*buffer) != '\0' && *(*buffer) != ']') {
// Try for the MIValue first
value = processMIValue(buffer);
if (value != NULL) {
MIListAdd(valueList, (void *)value);
} else {
result = processMIResult(buffer);
if (result != NULL) {
MIListAdd(resultList, (void *)result);
}
}
if (*(*buffer) != '\0' && *(*buffer) == ',') {
(*buffer)++;
}
}
if (*(*buffer) != '\0' && *(*buffer) == ']') {
(*buffer)++;
}
list->values = valueList;
list->results = resultList;
return list;
}
/*
* MI C-String rather MICOnst values are enclose in double quotes
* and any double quotes or backslash in the string are escaped.
* Assuming the starting double quote was removed.
* This method will stop at the closing double quote remove the extra
* backslach escaping and return the string __without__ the enclosing double quotes
* The orignal StringBuffer will move forward.
*/
static char *
translateCString(char **buffer)
{
int escape = 0;
int closingQuotes = 0;
char *s = *buffer;
char *sb = strdup(s);
char *p = sb;
for (; *s != '\0' && !closingQuotes; s++) {
if (*s == '\\') {
if (escape) {
*p++ = *s;
escape = 0;
} else {
escape = 1;
}
} else if (*s == '"') {
if (escape) {
*p++ = *s;
escape = 0;
} else {
// Bail out.
closingQuotes = 1;
}
} else {
if (escape) {
*p++ = '\\';
}
*p++ = *s;
escape = 0;
}
}
*buffer = s;
*p++ = '\0';
return sb;
}