mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
806 lines
14 KiB
C++
806 lines
14 KiB
C++
#include "MIValue.h"
|
|
|
|
//#define MITUPLE_DUMP_MARKERS
|
|
|
|
static MIValue &NullMIValue(void)
|
|
{
|
|
static MIValue v;
|
|
return v;
|
|
}
|
|
|
|
static MIValue &ErrorMIValue(String const &msg)
|
|
{
|
|
static MIValue v;
|
|
v.SetError(msg);
|
|
return v;
|
|
}
|
|
|
|
bool MIValue::expect(String const &where, char exp, int i, String const &s)
|
|
{
|
|
if(s[i] == exp)
|
|
return true;
|
|
int start = i - 30;
|
|
if(start < 0)
|
|
start = 0;
|
|
if(!s[i])
|
|
SetError(Format(where + " : Expected '%c', got end of string at pos %d around '%s'", exp, i, s.Mid(start, 60)));
|
|
else
|
|
SetError(Format(where + " : Expected '%c', got '%c' at pos %d in '%s'", exp, s[i], i, s.Mid(start, 60)));
|
|
return false;
|
|
}
|
|
|
|
static char backslash(String const &s, int &i)
|
|
{
|
|
i++;
|
|
if(IsDigit(s[i]))
|
|
{
|
|
char c = (s[i]-'0')*64 + (s[i+1]-'0')*8 + s[i+2]-'0';
|
|
i += 2;
|
|
return c;
|
|
}
|
|
|
|
// control chars and octals
|
|
switch(s[i])
|
|
{
|
|
case 'a' :
|
|
return '\a';
|
|
case 'b' :
|
|
return '\b';
|
|
case 'f' :
|
|
return '\f';
|
|
case 'n' :
|
|
return '\n';
|
|
case 'r' :
|
|
return '\r';
|
|
case 't' :
|
|
return '\t';
|
|
case 'v' :
|
|
return '\v';
|
|
default:
|
|
return s[i];
|
|
}
|
|
}
|
|
|
|
|
|
int MIValue::ParsePair(String &name, MIValue &val, String const &s, int i)
|
|
{
|
|
name.Clear();
|
|
val.Clear();
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
if(!s[i])
|
|
{
|
|
SetError("ParsePair:Unexpected end of string");
|
|
return i;
|
|
}
|
|
|
|
// is starting wirh '[' or '{' take it as a value with empty name
|
|
if(s[i] == '{' || s[i] == '[')
|
|
{
|
|
name = "<UNNAMED>";
|
|
return val.ParseTuple(s, i);
|
|
}
|
|
else
|
|
{
|
|
int aCount = 0;
|
|
while(s[i] && ((s[i] != '=' && s[i] != '}' && s[i] != ']' && s[i] != ',') || aCount))
|
|
{
|
|
if(s[i] == '<')
|
|
aCount++;
|
|
else if(s[i] == '>')
|
|
aCount--;
|
|
if(s[i] == '\\')
|
|
name.Cat(backslash(s, i));
|
|
else
|
|
name.Cat(s[i]);
|
|
i++;
|
|
|
|
// skip blanks if not inside <>
|
|
/*
|
|
if(!aCount)
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
*/
|
|
}
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
|
|
if(s[i] != '=')
|
|
{
|
|
// we take the data without = as the value part
|
|
// of keyless tuple...
|
|
val.Set(name);
|
|
name = "<UNNAMED>";
|
|
return i;
|
|
}
|
|
i++;
|
|
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
}
|
|
|
|
// skip address part before a tuple start, if any... it's useless and confuses the parser
|
|
if(s[i] == '@')
|
|
{
|
|
int j = i;
|
|
while(s[j] && s[j] != ':')
|
|
j++;
|
|
if(s[j] == ':')
|
|
j++;
|
|
while(s[j] && IsSpace(s[j]))
|
|
j++;
|
|
if(s[j] == '{')
|
|
i = j;
|
|
}
|
|
|
|
switch(s[i])
|
|
{
|
|
case '"':
|
|
i = val.ParseString(s, i);
|
|
break;
|
|
break;
|
|
|
|
case '[':
|
|
i = val.ParseArray(s, i);
|
|
break;
|
|
|
|
case '{':
|
|
i = val.ParseTuple(s, i);
|
|
break;
|
|
|
|
default:
|
|
i = val.ParseUnquotedString(s, i);
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
int MIValue::ParseTuple(String const &s, int i)
|
|
{
|
|
Clear();
|
|
type = MITuple;
|
|
|
|
// drop opening delimiter
|
|
if(!expect("ParseTuple", '{', i, s))
|
|
return s.GetCount();
|
|
i++;
|
|
while(s[i] && s[i] != '}')
|
|
{
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
String name;
|
|
MIValue val;
|
|
i = ParsePair(name, val, s, i);
|
|
tuple.AddPick(name, pick(val));
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
if(s[i] == '}')
|
|
break;
|
|
if(!expect("ParseTuple", ',', i, s))
|
|
return s.GetCount();
|
|
i++;
|
|
}
|
|
|
|
return i + 1;
|
|
}
|
|
|
|
int MIValue::ParseArray(String const &s, int i)
|
|
{
|
|
Clear();
|
|
type = MIArray;
|
|
|
|
// drop opening delimiter
|
|
if(!expect("ParseArray", '[', i, s))
|
|
return s.GetCount();
|
|
i++;
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
while(s[i] && s[i] != ']')
|
|
{
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
String name;
|
|
MIValue val;
|
|
if(s[i] == '[')
|
|
i = val.ParseArray(s, i);
|
|
else if(s[i] == '{')
|
|
i = val.ParseTuple(s, i);
|
|
else if(s[i] == '"')
|
|
i = val.ParseString(s, i);
|
|
else if(s[i] == '<')
|
|
i = val.ParseAngle(s, i);
|
|
else
|
|
i = ParsePair(name, val, s, i);
|
|
array.Add() = pick(val);
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
if(s[i] == ']')
|
|
break;
|
|
if(!expect("ParseArray", ',', i, s))
|
|
return s.GetCount();
|
|
i++;
|
|
}
|
|
return i + 1;
|
|
}
|
|
|
|
int MIValue::ParseString(String const &s, int i)
|
|
{
|
|
Clear();
|
|
type = MIString;
|
|
|
|
if(!expect("ParseString", '"', i, s))
|
|
return s.GetCount();
|
|
i++;
|
|
while(s[i])
|
|
{
|
|
// verbatim if escaped
|
|
if(s[i] == '\\')
|
|
string.Cat(backslash(s, i));
|
|
else if(s[i] == '"')
|
|
{
|
|
i++;
|
|
break;
|
|
}
|
|
else
|
|
string.Cat(s[i]);
|
|
i++;
|
|
}
|
|
if(!expect("ParseString", '"', i-1, s))
|
|
return s.GetCount();
|
|
|
|
return i;
|
|
}
|
|
|
|
int MIValue::ParseAngle(String const &s, int i)
|
|
{
|
|
Clear();
|
|
type = MIString;
|
|
int aCount = 0;
|
|
|
|
if(!expect("ParseAngle", '<', i, s))
|
|
return s.GetCount();
|
|
string = "<";
|
|
aCount++;
|
|
i++;
|
|
while(s[i])
|
|
{
|
|
// verbatim if escaped
|
|
if(s[i] == '\\')
|
|
string.Cat(backslash(s, i));
|
|
else if(s[i] == '>' && !--aCount)
|
|
{
|
|
i++;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
string.Cat(s[i]);
|
|
if(s[i] == '<')
|
|
aCount++;
|
|
}
|
|
i++;
|
|
}
|
|
if(!expect("ParseAngle", '>', i-1, s))
|
|
return s.GetCount();
|
|
string.Cat('>');
|
|
|
|
return i;
|
|
}
|
|
|
|
// sigh
|
|
static bool comma(String const &s, int i)
|
|
{
|
|
if(s[i] != ',')
|
|
return false;
|
|
if(!i)
|
|
return true;
|
|
if(IsDigit(s[i-1]) && IsDigit(s[i+1]))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// we can hava a non-quoted string... so we read up
|
|
// to terminator, which can be '}', ']' or ','
|
|
int MIValue::ParseUnquotedString(String const &s, int i)
|
|
{
|
|
String valStr;
|
|
int aCount = 0;
|
|
bool inQuote = false;
|
|
|
|
while(s[i] && ((s[i] != '=' && s[i] != '}' && s[i] != ']' && !comma(s, i)) || inQuote || aCount))
|
|
{
|
|
valStr.Cat(s[i]);
|
|
if(s[i] == '\\')
|
|
{
|
|
i++;
|
|
if(s[i])
|
|
valStr.Cat(s[i++]);
|
|
continue;
|
|
}
|
|
if(s[i] == '<' && !inQuote)
|
|
aCount++;
|
|
else if(s[i] == '>' && !inQuote)
|
|
aCount--;
|
|
else if(s[i] == '"' && !aCount)
|
|
inQuote = !inQuote;
|
|
i++;
|
|
}
|
|
type = MIString;
|
|
string = valStr;
|
|
|
|
return i;
|
|
}
|
|
|
|
int MIValue::Parse(String const &s, int i)
|
|
{
|
|
// if starts with '"', it's a string
|
|
// if starts with '[', it's an array
|
|
// if starts with '{', it's a tuple
|
|
// otherwise, it can be a sequence of pair name="value" which is stored like a tuple
|
|
// latter case is an example o bad design of MI interface....
|
|
Clear();
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
if(s[i] == '"')
|
|
return ParseString(s, i);
|
|
else if(s[i] == '<')
|
|
return ParseAngle(s, i);
|
|
else if(s[i] == '[')
|
|
return ParseArray(s, i);
|
|
else if(s[i] == '{')
|
|
return ParseTuple(s, i);
|
|
else
|
|
{
|
|
String name;
|
|
MIValue val;
|
|
type = MITuple;
|
|
while(s[i])
|
|
{
|
|
i = ParsePair(name, val, s, i);
|
|
tuple.AddPick(name, pick(val));
|
|
while(s[i] && isspace(s[i]))
|
|
i++;
|
|
if(s[i] != ',')
|
|
break;
|
|
i++;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
MIValue &MIValue::operator=(MIValue&& v)
|
|
{
|
|
Clear();
|
|
type = pick(v.type);
|
|
switch(type)
|
|
{
|
|
case MIString:
|
|
string = v.string;
|
|
break;
|
|
case MIArray:
|
|
array = pick(v.array);
|
|
break;
|
|
case MITuple:
|
|
tuple = pick(v.tuple);
|
|
break;
|
|
default:
|
|
SetError("Unknown MIValue type");
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MIValue::MIValue()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
MIValue::MIValue(MIValue&& v)
|
|
{
|
|
Clear();
|
|
type = v.type;
|
|
switch(type)
|
|
{
|
|
case MIString:
|
|
string = v.string;
|
|
break;
|
|
case MIArray:
|
|
array = pick(v.array);
|
|
break;
|
|
case MITuple:
|
|
tuple = pick(v.tuple);
|
|
break;
|
|
default:
|
|
SetError("Unknown MIValue type");
|
|
}
|
|
}
|
|
|
|
MIValue::MIValue(String const &s)
|
|
{
|
|
Parse(s);
|
|
|
|
// tuple with 1 element and unnamed key is a string
|
|
if(IsTuple() && tuple.GetCount() == 1 && tuple.GetKey(0) == "<UNNAMED>")
|
|
{
|
|
type = MIString;
|
|
string = tuple[0];
|
|
tuple.Clear();
|
|
}
|
|
}
|
|
|
|
MIValue &MIValue::operator=(String const &s)
|
|
{
|
|
Parse(s);
|
|
// tuple with 1 element and unnamed key is a string
|
|
if(IsTuple() && tuple.GetCount() == 1 && tuple.GetKey(0) == "<UNNAMED>")
|
|
{
|
|
type = MIString;
|
|
string = tuple[0];
|
|
tuple.Clear();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void MIValue::Clear()
|
|
{
|
|
type = MIString;
|
|
string = "";
|
|
array.Clear();
|
|
tuple.Clear();
|
|
}
|
|
|
|
// sets value to an error condition
|
|
MIValue &MIValue::SetError(String const &msg)
|
|
{
|
|
type = MIString;
|
|
string = "error:" + msg;
|
|
return *this;
|
|
}
|
|
|
|
// check if value contains an error
|
|
bool MIValue::IsError(void) const
|
|
{
|
|
return type == MIString && string.StartsWith("error:");
|
|
}
|
|
|
|
// check for emptyness
|
|
bool MIValue::IsEmpty(void) const
|
|
{
|
|
return type == MIString && string == "";
|
|
}
|
|
|
|
// simple accessors
|
|
int MIValue::GetCount(void) const
|
|
{
|
|
if(IsError())
|
|
return 0;
|
|
if(type == MIArray)
|
|
return array.GetCount();
|
|
else if(type == MITuple)
|
|
return tuple.GetCount();
|
|
else
|
|
return string.GetCount();
|
|
}
|
|
|
|
int MIValue::Find(const char *key) const
|
|
{
|
|
if(type != MITuple)
|
|
return -1;
|
|
return tuple.Find(key);
|
|
}
|
|
|
|
MIValue &MIValue::Get(int i)
|
|
{
|
|
if(IsError())
|
|
return *this;
|
|
if(type == MIArray)
|
|
return array[i];
|
|
if(type == MITuple)
|
|
return tuple[i];
|
|
return ErrorMIValue("Not an Array value type");
|
|
}
|
|
|
|
MIValue const &MIValue::Get(int i) const
|
|
{
|
|
if(IsError())
|
|
return *this;
|
|
if(type == MIArray)
|
|
return array[i];
|
|
if(type == MITuple)
|
|
return tuple[i];
|
|
return ErrorMIValue("Not an Array value type");
|
|
}
|
|
|
|
MIValue &MIValue::Get(const char *key)
|
|
{
|
|
if(type != MITuple)
|
|
return ErrorMIValue("Not a Tuple value type");
|
|
if(tuple.Find(key) < 0)
|
|
return ErrorMIValue(String("key '") + key + "' not found");
|
|
return tuple.Get(key);
|
|
}
|
|
|
|
MIValue const &MIValue::Get(const char *key) const
|
|
{
|
|
if(type != MITuple)
|
|
return ErrorMIValue("Not a Tuple value type");
|
|
if(tuple.Find(key) < 0)
|
|
return ErrorMIValue(String("key '") + key + "' not found");
|
|
return tuple.Get(key);
|
|
}
|
|
|
|
String &MIValue::Get(void)
|
|
{
|
|
if(type != MIString)
|
|
return ErrorMIValue("Not a String value type");
|
|
return string;
|
|
}
|
|
|
|
String const &MIValue::Get(void) const
|
|
{
|
|
if(type != MIString)
|
|
return ErrorMIValue("Not a String value type");
|
|
return string;
|
|
}
|
|
|
|
// tuple string member accessor with default value if not found
|
|
String MIValue::Get(const char *key, const char *def) const
|
|
{
|
|
if(type != MITuple)
|
|
return ErrorMIValue("Not a Tuple value type");
|
|
int i = tuple.Find(key);
|
|
if(i >= 0)
|
|
{
|
|
if(tuple[i].type != MIString)
|
|
return def;
|
|
return tuple[i].Get();
|
|
}
|
|
else
|
|
return def;
|
|
}
|
|
|
|
// gets key by index for tuple values
|
|
String MIValue::GetKey(int idx) const
|
|
{
|
|
if(type != MITuple)
|
|
return ErrorMIValue("Not a Tuple value type");
|
|
return tuple.GetKey(idx);
|
|
}
|
|
|
|
void MIValue::Set(String const &s)
|
|
{
|
|
Clear();
|
|
type = MIString;
|
|
string = s;
|
|
}
|
|
|
|
// data dump
|
|
#ifdef MITUPLE_DUMP_MARKERS
|
|
#define MARK_STRING "<STRING>"
|
|
#define MARK_ARRAY "<ARRAY>"
|
|
#define MARK_TUPLE "<TUPLE>"
|
|
#else
|
|
#define MARK_STRING ""
|
|
#define MARK_ARRAY ""
|
|
#define MARK_TUPLE ""
|
|
#endif
|
|
|
|
// dumps a string with special chars inside
|
|
String MIValue::Dump(String const &s)
|
|
{
|
|
String res;
|
|
for(int i = 0; i < s.GetCharCount(); i++)
|
|
{
|
|
byte c = s[i];
|
|
if(isprint(c))
|
|
res << c;
|
|
else
|
|
res += Format("\\%03o", c);
|
|
}
|
|
return res;
|
|
}
|
|
String MIValue::Dump(int level) const
|
|
{
|
|
String spacer(' ', level);
|
|
switch(type)
|
|
{
|
|
case MIString:
|
|
return spacer + MARK_STRING + Dump(string);
|
|
|
|
case MITuple:
|
|
{
|
|
String s = spacer + MARK_TUPLE + "{\n";
|
|
level += 4;
|
|
spacer = String(' ', level);
|
|
for(int i = 0; i < tuple.GetCount(); i++)
|
|
{
|
|
String s1 = spacer + tuple.GetKey(i) + "=";
|
|
s += s1;
|
|
MIValue const &val = tuple[i];
|
|
if(val.type == MIString)
|
|
s += val.Dump();
|
|
else
|
|
{
|
|
s += '\n' + val.Dump(level + 4);
|
|
s = s.Left(s.GetCount()-1);
|
|
}
|
|
if(i < tuple.GetCount() - 1)
|
|
s += ',';
|
|
s += '\n';
|
|
}
|
|
level -= 4;
|
|
spacer = String(' ', level);
|
|
s += spacer + "}\n";
|
|
return s;
|
|
}
|
|
|
|
case MIArray:
|
|
{
|
|
String s = spacer + MARK_ARRAY + "[ \n";
|
|
level += 4;
|
|
for(int i = 0; i < array.GetCount(); i++)
|
|
{
|
|
MIValue const &val = array[i];
|
|
s += val.Dump(level);
|
|
if(val.type != MIString)
|
|
s = s.Left(s.GetCount()-1);
|
|
if(i < array.GetCount() - 1)
|
|
s += ',';
|
|
s += '\n';
|
|
}
|
|
s += spacer + "]\n";
|
|
return s;
|
|
}
|
|
|
|
default:
|
|
return spacer + "*UNKNOWN MIVALUE TYPE*";
|
|
}
|
|
}
|
|
|
|
// finds breakpoint data given file and line
|
|
MIValue &MIValue::FindBreakpoint(String const &file, int line)
|
|
{
|
|
MIValue &body = Get("body");
|
|
if(body.IsError() || !body.IsArray())
|
|
return NullMIValue();
|
|
for(int i = 0; i < body.GetCount(); i++)
|
|
{
|
|
MIValue &bp = body[i];
|
|
if(bp.IsError() || !bp.IsTuple())
|
|
return NullMIValue();
|
|
if(bp["file"] == file && atoi(bp["line"].Get()) == line)
|
|
return bp;
|
|
}
|
|
return NullMIValue();
|
|
}
|
|
|
|
static String PackName(String const &name)
|
|
{
|
|
String res;
|
|
const char *c = ~name;
|
|
while(*c)
|
|
{
|
|
if(!IsSpace(*c))
|
|
res.Cat(*c);
|
|
c++;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// packs names inside tuples -- to make type recognition easy
|
|
void MIValue::PackNames(void)
|
|
{
|
|
if(type == MITuple)
|
|
{
|
|
for(int i = 0; i < tuple.GetCount(); i++)
|
|
{
|
|
tuple.SetKey(i, PackName(tuple.GetKey(i)));
|
|
tuple[i].PackNames();
|
|
}
|
|
}
|
|
else if(type == MIArray)
|
|
{
|
|
for(int i = 0; i < array.GetCount(); i++)
|
|
array[i].PackNames();
|
|
}
|
|
}
|
|
|
|
// fix arrays -- i.e. replace a tuple containing ALL unnamed elements
|
|
// with the corresponding array
|
|
// ( gdb evaluator array data is returned as tuple with {} instead []=
|
|
void MIValue::FixArrays(void)
|
|
{
|
|
|
|
bool named = false;
|
|
if(IsTuple())
|
|
{
|
|
for(int iVal = 0; iVal < tuple.GetCount(); iVal++)
|
|
if(tuple.GetKey(iVal) != "<UNNAMED>")
|
|
{
|
|
named = true;
|
|
break;
|
|
}
|
|
if(!named)
|
|
{
|
|
array.Clear();
|
|
for(int iVal = 0; iVal < tuple.GetCount(); iVal++)
|
|
array.Add() = pick(tuple[iVal]);
|
|
tuple.Clear();
|
|
type = MIArray;
|
|
}
|
|
}
|
|
if(IsTuple() || IsArray())
|
|
for(int i = 0; i < GetCount(); i++)
|
|
Get(i).FixArrays();
|
|
}
|
|
|
|
// add an item to a tuple
|
|
MIValue &MIValue::Add(String const &key, MIValue&& v)
|
|
{
|
|
if(IsEmpty())
|
|
{
|
|
Clear();
|
|
type = MITuple;
|
|
}
|
|
if(type != MITuple)
|
|
return ErrorMIValue("Not a Tuple value type");
|
|
tuple.AddPick(key, pick(v));
|
|
return *this;
|
|
}
|
|
|
|
MIValue &MIValue::Add(String const &key, String const &data)
|
|
{
|
|
MIValue v;
|
|
v.Set(data);
|
|
return Add(key, pick(v));
|
|
}
|
|
|
|
MIValue &MIValue::FindAdd(String const &key, String const &data)
|
|
{
|
|
if(IsEmpty())
|
|
{
|
|
Clear();
|
|
type = MITuple;
|
|
}
|
|
if(type != MITuple)
|
|
return ErrorMIValue("Not a Tuple value type");
|
|
int idx = tuple.Find(key);
|
|
MIValue v;
|
|
v.Set(data);
|
|
if(idx >= 0)
|
|
tuple[idx] = pick(v);
|
|
else
|
|
tuple.AddPick(key, pick(v));
|
|
return *this;
|
|
}
|
|
|
|
// add an item to an array
|
|
MIValue &MIValue::Add(MIValue&& v)
|
|
{
|
|
if(IsEmpty())
|
|
{
|
|
Clear();
|
|
type = MIArray;
|
|
}
|
|
if(type != MIArray)
|
|
return ErrorMIValue("Not a Array value type");
|
|
array.AddPick(pick(v));
|
|
return *this;
|
|
}
|
|
|
|
MIValue &MIValue::Add(String const &data)
|
|
{
|
|
MIValue v;
|
|
v.Set(data);
|
|
return Add(pick(v));
|
|
}
|
|
|
|
// remove a tuple key
|
|
MIValue &MIValue::Remove(String const &key)
|
|
{
|
|
if(type != MITuple)
|
|
return *this;
|
|
tuple.RemoveKey(key);
|
|
return *this;
|
|
}
|