| /****************************************************************************** | |
| * 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; | |
| } |