786 lines
26 KiB
C#
786 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace WulaFallenEmpire.EventSystem.AI.Utils
|
|
{
|
|
public sealed class ToolCallInfo
|
|
{
|
|
public string Id;
|
|
public string Name;
|
|
public Dictionary<string, object> Arguments;
|
|
public string ArgumentsJson;
|
|
}
|
|
|
|
public static class JsonToolCallParser
|
|
{
|
|
public static bool TryParseToolCalls(string input, out List<ToolCallInfo> toolCalls)
|
|
{
|
|
toolCalls = null;
|
|
if (string.IsNullOrWhiteSpace(input)) return false;
|
|
|
|
if (!TryParseValue(input, out object root)) return false;
|
|
if (root is not Dictionary<string, object> obj) return false;
|
|
|
|
if (!TryGetValue(obj, "tool_calls", out object callsObj)) return false;
|
|
if (callsObj is not List<object> callsList) return false;
|
|
|
|
var parsedCalls = new List<ToolCallInfo>();
|
|
foreach (var entry in callsList)
|
|
{
|
|
if (entry is not Dictionary<string, object> callObj) continue;
|
|
|
|
string id = TryGetString(callObj, "id");
|
|
string name = null;
|
|
object argsObj = null;
|
|
|
|
if (TryGetValue(callObj, "function", out object fnObj) && fnObj is Dictionary<string, object> fnDict)
|
|
{
|
|
name = TryGetString(fnDict, "name");
|
|
TryGetValue(fnDict, "arguments", out argsObj);
|
|
}
|
|
else
|
|
{
|
|
name = TryGetString(callObj, "name");
|
|
TryGetValue(callObj, "arguments", out argsObj);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(name)) continue;
|
|
|
|
if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson))
|
|
{
|
|
args = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
argsJson = "{}";
|
|
}
|
|
|
|
parsedCalls.Add(new ToolCallInfo
|
|
{
|
|
Id = id,
|
|
Name = name.Trim(),
|
|
Arguments = args,
|
|
ArgumentsJson = argsJson
|
|
});
|
|
}
|
|
|
|
toolCalls = parsedCalls;
|
|
return true;
|
|
}
|
|
|
|
public static bool TryParseToolCallsFromText(string input, out List<ToolCallInfo> toolCalls, out string jsonFragment)
|
|
{
|
|
toolCalls = null;
|
|
jsonFragment = null;
|
|
if (string.IsNullOrWhiteSpace(input)) return false;
|
|
|
|
string trimmed = input.Trim();
|
|
if (TryParseToolCalls(trimmed, out toolCalls))
|
|
{
|
|
jsonFragment = trimmed;
|
|
return true;
|
|
}
|
|
|
|
if (TryParseToolCallsFromQwenText(trimmed, out toolCalls, out jsonFragment))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int firstBrace = trimmed.IndexOf('{');
|
|
int lastBrace = trimmed.LastIndexOf('}');
|
|
if (firstBrace >= 0 && lastBrace > firstBrace)
|
|
{
|
|
string candidate = trimmed.Substring(firstBrace, lastBrace - firstBrace + 1);
|
|
if (TryParseToolCalls(candidate, out toolCalls))
|
|
{
|
|
jsonFragment = candidate;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (TryParseToolCallsLoose(trimmed, out toolCalls, out jsonFragment))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
private static bool TryParseToolCallsFromQwenText(string input, out List<ToolCallInfo> toolCalls, out string jsonFragment)
|
|
{
|
|
toolCalls = null;
|
|
jsonFragment = null;
|
|
if (string.IsNullOrWhiteSpace(input)) return false;
|
|
|
|
const string startTag = "<tool_call>";
|
|
const string endTag = "</tool_call>";
|
|
int index = 0;
|
|
var parsedCalls = new List<ToolCallInfo>();
|
|
|
|
while (index < input.Length)
|
|
{
|
|
int start = IndexOfIgnoreCase(input, startTag, index);
|
|
if (start < 0) break;
|
|
int end = IndexOfIgnoreCase(input, endTag, start + startTag.Length);
|
|
if (end < 0) break;
|
|
|
|
string inner = input.Substring(start + startTag.Length, end - (start + startTag.Length)).Trim();
|
|
if (TryParseObject(inner, out Dictionary<string, object> obj))
|
|
{
|
|
string name = TryGetString(obj, "name");
|
|
if (!string.IsNullOrWhiteSpace(name))
|
|
{
|
|
object argsObj = null;
|
|
TryGetValue(obj, "arguments", out argsObj);
|
|
if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson))
|
|
{
|
|
args = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
argsJson = "{}";
|
|
}
|
|
string id = TryGetString(obj, "id");
|
|
parsedCalls.Add(new ToolCallInfo
|
|
{
|
|
Id = id,
|
|
Name = name.Trim(),
|
|
Arguments = args,
|
|
ArgumentsJson = argsJson
|
|
});
|
|
}
|
|
}
|
|
|
|
index = end + endTag.Length;
|
|
}
|
|
|
|
if (parsedCalls.Count == 0) return false;
|
|
|
|
toolCalls = parsedCalls;
|
|
var callEntries = new List<object>();
|
|
foreach (var call in parsedCalls)
|
|
{
|
|
var entry = new Dictionary<string, object>
|
|
{
|
|
["type"] = "function",
|
|
["function"] = new Dictionary<string, object>
|
|
{
|
|
["name"] = call.Name ?? "",
|
|
["arguments"] = call.Arguments ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
|
}
|
|
};
|
|
if (!string.IsNullOrWhiteSpace(call.Id))
|
|
{
|
|
entry["id"] = call.Id;
|
|
}
|
|
callEntries.Add(entry);
|
|
}
|
|
|
|
jsonFragment = SerializeToJson(new Dictionary<string, object>
|
|
{
|
|
["tool_calls"] = callEntries
|
|
});
|
|
return true;
|
|
}
|
|
|
|
public static bool TryParseObjectFromText(string input, out Dictionary<string, object> obj, out string jsonFragment)
|
|
{
|
|
obj = null;
|
|
jsonFragment = null;
|
|
if (string.IsNullOrWhiteSpace(input)) return false;
|
|
|
|
string trimmed = input.Trim();
|
|
if (TryParseObject(trimmed, out obj))
|
|
{
|
|
jsonFragment = trimmed;
|
|
return true;
|
|
}
|
|
|
|
int firstBrace = trimmed.IndexOf('{');
|
|
int lastBrace = trimmed.LastIndexOf('}');
|
|
if (firstBrace >= 0 && lastBrace > firstBrace)
|
|
{
|
|
string candidate = trimmed.Substring(firstBrace, lastBrace - firstBrace + 1);
|
|
if (TryParseObject(candidate, out obj))
|
|
{
|
|
jsonFragment = candidate;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryParseToolCallsLoose(string input, out List<ToolCallInfo> toolCalls, out string jsonFragment)
|
|
{
|
|
toolCalls = null;
|
|
jsonFragment = null;
|
|
if (string.IsNullOrWhiteSpace(input)) return false;
|
|
|
|
string cleaned = input.Trim();
|
|
|
|
int toolCallsIndex = IndexOfIgnoreCase(cleaned, "\"tool_calls\"");
|
|
if (toolCallsIndex < 0) return false;
|
|
|
|
int arrayStart = cleaned.IndexOf('[', toolCallsIndex);
|
|
if (arrayStart < 0) return false;
|
|
|
|
if (!TryExtractJsonBlock(cleaned, arrayStart, '[', ']', out string arrayBlock)) return false;
|
|
|
|
var callObjects = ExtractJsonObjects(arrayBlock);
|
|
if (callObjects.Count == 0) return false;
|
|
|
|
var parsedCalls = new List<ToolCallInfo>();
|
|
foreach (var callObj in callObjects)
|
|
{
|
|
if (callObj == null || callObj.Count == 0) continue;
|
|
|
|
string id = TryGetString(callObj, "id");
|
|
string name = null;
|
|
object argsObj = null;
|
|
|
|
if (TryGetValue(callObj, "function", out object fnObj) && fnObj is Dictionary<string, object> fnDict)
|
|
{
|
|
name = TryGetString(fnDict, "name");
|
|
TryGetValue(fnDict, "arguments", out argsObj);
|
|
}
|
|
else
|
|
{
|
|
name = TryGetString(callObj, "name");
|
|
TryGetValue(callObj, "arguments", out argsObj);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(name)) continue;
|
|
|
|
if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson))
|
|
{
|
|
args = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
argsJson = "{}";
|
|
}
|
|
|
|
parsedCalls.Add(new ToolCallInfo
|
|
{
|
|
Id = id,
|
|
Name = name.Trim(),
|
|
Arguments = args,
|
|
ArgumentsJson = argsJson
|
|
});
|
|
}
|
|
|
|
if (parsedCalls.Count == 0) return false;
|
|
|
|
toolCalls = parsedCalls;
|
|
jsonFragment = arrayBlock;
|
|
return true;
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> ExtractJsonObjects(string input)
|
|
{
|
|
var objects = new List<Dictionary<string, object>>();
|
|
if (string.IsNullOrWhiteSpace(input)) return objects;
|
|
|
|
int i = 0;
|
|
while (i < input.Length)
|
|
{
|
|
if (input[i] != '{')
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (!TryExtractJsonBlock(input, i, '{', '}', out string objBlock))
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (TryParseObject(objBlock, out Dictionary<string, object> obj))
|
|
{
|
|
objects.Add(obj);
|
|
}
|
|
|
|
i += Math.Max(1, objBlock.Length);
|
|
}
|
|
|
|
return objects;
|
|
}
|
|
|
|
private static bool TryExtractJsonBlock(string input, int startIndex, char openChar, char closeChar, out string block)
|
|
{
|
|
block = null;
|
|
if (string.IsNullOrWhiteSpace(input) || startIndex < 0 || startIndex >= input.Length) return false;
|
|
|
|
int depth = 0;
|
|
bool inString = false;
|
|
bool escape = false;
|
|
int start = -1;
|
|
|
|
for (int i = startIndex; i < input.Length; i++)
|
|
{
|
|
char c = input[i];
|
|
if (escape)
|
|
{
|
|
escape = false;
|
|
}
|
|
else if (c == '\\')
|
|
{
|
|
if (inString) escape = true;
|
|
}
|
|
else if (c == '\"')
|
|
{
|
|
inString = !inString;
|
|
}
|
|
|
|
if (inString) continue;
|
|
|
|
if (c == openChar)
|
|
{
|
|
if (depth == 0)
|
|
{
|
|
start = i;
|
|
}
|
|
depth++;
|
|
continue;
|
|
}
|
|
if (c == closeChar)
|
|
{
|
|
depth--;
|
|
if (depth == 0 && start >= 0)
|
|
{
|
|
block = input.Substring(start, i - start + 1);
|
|
return true;
|
|
}
|
|
if (depth < 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static int IndexOfIgnoreCase(string input, string value)
|
|
{
|
|
return input?.IndexOf(value ?? "", StringComparison.OrdinalIgnoreCase) ?? -1;
|
|
}
|
|
private static int IndexOfIgnoreCase(string input, string value, int startIndex)
|
|
{
|
|
return input?.IndexOf(value ?? "", startIndex, StringComparison.OrdinalIgnoreCase) ?? -1;
|
|
}
|
|
|
|
public static bool TryParseObject(string json, out Dictionary<string, object> obj)
|
|
{
|
|
obj = null;
|
|
if (string.IsNullOrWhiteSpace(json)) return false;
|
|
if (!TryParseValue(json, out object value)) return false;
|
|
if (value is not Dictionary<string, object> dict) return false;
|
|
obj = dict;
|
|
return true;
|
|
}
|
|
|
|
public static bool LooksLikeJson(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text)) return false;
|
|
string trimmed = text.TrimStart();
|
|
return trimmed.StartsWith("{", StringComparison.Ordinal) || trimmed.StartsWith("[", StringComparison.Ordinal);
|
|
}
|
|
|
|
public static string SerializeToJson(object value)
|
|
{
|
|
var sb = new StringBuilder();
|
|
AppendValue(sb, value);
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static void AppendValue(StringBuilder sb, object value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
sb.Append("null");
|
|
return;
|
|
}
|
|
|
|
if (value is string s)
|
|
{
|
|
sb.Append('\"').Append(EscapeJson(s)).Append('\"');
|
|
return;
|
|
}
|
|
|
|
if (value is bool b)
|
|
{
|
|
sb.Append(b ? "true" : "false");
|
|
return;
|
|
}
|
|
|
|
if (value is double d)
|
|
{
|
|
sb.Append(d.ToString("0.################", CultureInfo.InvariantCulture));
|
|
return;
|
|
}
|
|
|
|
if (value is float f)
|
|
{
|
|
sb.Append(f.ToString("0.################", CultureInfo.InvariantCulture));
|
|
return;
|
|
}
|
|
|
|
if (value is int or long or short or byte)
|
|
{
|
|
sb.Append(Convert.ToString(value, CultureInfo.InvariantCulture));
|
|
return;
|
|
}
|
|
|
|
if (value is Dictionary<string, object> obj)
|
|
{
|
|
sb.Append('{');
|
|
bool first = true;
|
|
foreach (var kvp in obj)
|
|
{
|
|
if (!first) sb.Append(',');
|
|
first = false;
|
|
sb.Append('\"').Append(EscapeJson(kvp.Key)).Append('\"').Append(':');
|
|
AppendValue(sb, kvp.Value);
|
|
}
|
|
sb.Append('}');
|
|
return;
|
|
}
|
|
|
|
if (value is List<object> list)
|
|
{
|
|
sb.Append('[');
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
if (i > 0) sb.Append(',');
|
|
AppendValue(sb, list[i]);
|
|
}
|
|
sb.Append(']');
|
|
return;
|
|
}
|
|
|
|
sb.Append('\"').Append(EscapeJson(Convert.ToString(value, CultureInfo.InvariantCulture) ?? "")).Append('\"');
|
|
}
|
|
|
|
private static bool TryNormalizeArguments(object argsObj, out Dictionary<string, object> args, out string argsJson)
|
|
{
|
|
args = null;
|
|
argsJson = null;
|
|
|
|
if (argsObj == null)
|
|
{
|
|
args = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
argsJson = "{}";
|
|
return true;
|
|
}
|
|
|
|
if (argsObj is Dictionary<string, object> dict)
|
|
{
|
|
args = dict;
|
|
argsJson = SerializeToJson(dict);
|
|
return true;
|
|
}
|
|
|
|
if (argsObj is string s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s))
|
|
{
|
|
args = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
argsJson = "{}";
|
|
return true;
|
|
}
|
|
|
|
if (TryParseObject(s, out Dictionary<string, object> parsed))
|
|
{
|
|
args = parsed;
|
|
argsJson = SerializeToJson(parsed);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryParseValue(string json, out object value)
|
|
{
|
|
value = null;
|
|
var reader = new JsonReader(json);
|
|
if (!reader.TryReadValue(out value)) return false;
|
|
reader.SkipWhitespace();
|
|
return reader.IsAtEnd;
|
|
}
|
|
|
|
private static string EscapeJson(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value)) return "";
|
|
var sb = new StringBuilder();
|
|
foreach (char c in value)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '\\': sb.Append("\\\\"); break;
|
|
case '\"': sb.Append("\\\""); break;
|
|
case '\n': sb.Append("\\n"); break;
|
|
case '\r': sb.Append("\\r"); break;
|
|
case '\t': sb.Append("\\t"); break;
|
|
default:
|
|
if (c < 0x20)
|
|
{
|
|
sb.Append("\\u").Append(((int)c).ToString("x4", CultureInfo.InvariantCulture));
|
|
}
|
|
else
|
|
{
|
|
sb.Append(c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static bool TryGetValue(Dictionary<string, object> obj, string key, out object value)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
value = null;
|
|
return false;
|
|
}
|
|
foreach (var kvp in obj)
|
|
{
|
|
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
value = kvp.Value;
|
|
return true;
|
|
}
|
|
}
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
private static string TryGetString(Dictionary<string, object> obj, string key)
|
|
{
|
|
if (TryGetValue(obj, key, out object value) && value != null)
|
|
{
|
|
return Convert.ToString(value, CultureInfo.InvariantCulture);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private sealed class JsonReader
|
|
{
|
|
private readonly string _text;
|
|
private int _index;
|
|
|
|
public JsonReader(string text)
|
|
{
|
|
_text = text ?? "";
|
|
_index = 0;
|
|
}
|
|
|
|
public bool IsAtEnd => _index >= _text.Length;
|
|
|
|
public void SkipWhitespace()
|
|
{
|
|
while (_index < _text.Length && char.IsWhiteSpace(_text[_index]))
|
|
{
|
|
_index++;
|
|
}
|
|
}
|
|
|
|
public bool TryReadValue(out object value)
|
|
{
|
|
value = null;
|
|
SkipWhitespace();
|
|
if (IsAtEnd) return false;
|
|
|
|
char c = _text[_index];
|
|
if (c == '{') return TryReadObject(out value);
|
|
if (c == '[') return TryReadArray(out value);
|
|
if (c == '\"') return TryReadString(out value);
|
|
if (c == '-' || char.IsDigit(c)) return TryReadNumber(out value);
|
|
if (TryReadLiteral("true")) { value = true; return true; }
|
|
if (TryReadLiteral("false")) { value = false; return true; }
|
|
if (TryReadLiteral("null")) { value = null; return true; }
|
|
return false;
|
|
}
|
|
|
|
private bool TryReadObject(out object value)
|
|
{
|
|
value = null;
|
|
if (!TryReadChar('{')) return false;
|
|
SkipWhitespace();
|
|
|
|
var dict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
if (TryReadChar('}'))
|
|
{
|
|
value = dict;
|
|
return true;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
SkipWhitespace();
|
|
if (!TryReadString(out object keyObj)) return false;
|
|
string key = keyObj as string ?? "";
|
|
SkipWhitespace();
|
|
if (!TryReadChar(':')) return false;
|
|
if (!TryReadValue(out object itemValue)) return false;
|
|
dict[key] = itemValue;
|
|
SkipWhitespace();
|
|
if (TryReadChar('}'))
|
|
{
|
|
value = dict;
|
|
return true;
|
|
}
|
|
if (!TryReadChar(',')) return false;
|
|
}
|
|
}
|
|
|
|
private bool TryReadArray(out object value)
|
|
{
|
|
value = null;
|
|
if (!TryReadChar('[')) return false;
|
|
SkipWhitespace();
|
|
|
|
var list = new List<object>();
|
|
if (TryReadChar(']'))
|
|
{
|
|
value = list;
|
|
return true;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (!TryReadValue(out object item)) return false;
|
|
list.Add(item);
|
|
SkipWhitespace();
|
|
if (TryReadChar(']'))
|
|
{
|
|
value = list;
|
|
return true;
|
|
}
|
|
if (!TryReadChar(',')) return false;
|
|
}
|
|
}
|
|
|
|
private bool TryReadString(out object value)
|
|
{
|
|
value = null;
|
|
if (!TryReadChar('\"')) return false;
|
|
var sb = new StringBuilder();
|
|
while (_index < _text.Length)
|
|
{
|
|
char c = _text[_index++];
|
|
if (c == '\"')
|
|
{
|
|
value = sb.ToString();
|
|
return true;
|
|
}
|
|
if (c == '\\')
|
|
{
|
|
if (_index >= _text.Length) return false;
|
|
char esc = _text[_index++];
|
|
switch (esc)
|
|
{
|
|
case '\"': sb.Append('\"'); break;
|
|
case '\\': sb.Append('\\'); break;
|
|
case '/': sb.Append('/'); break;
|
|
case 'b': sb.Append('\b'); break;
|
|
case 'f': sb.Append('\f'); break;
|
|
case 'n': sb.Append('\n'); break;
|
|
case 'r': sb.Append('\r'); break;
|
|
case 't': sb.Append('\t'); break;
|
|
case 'u':
|
|
if (_index + 4 > _text.Length) return false;
|
|
string hex = _text.Substring(_index, 4);
|
|
if (!int.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int code))
|
|
{
|
|
return false;
|
|
}
|
|
sb.Append((char)code);
|
|
_index += 4;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Append(c);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryReadNumber(out object value)
|
|
{
|
|
value = null;
|
|
int start = _index;
|
|
if (_text[_index] == '-') _index++;
|
|
|
|
while (_index < _text.Length && char.IsDigit(_text[_index]))
|
|
{
|
|
_index++;
|
|
}
|
|
|
|
bool hasDot = false;
|
|
if (_index < _text.Length && _text[_index] == '.')
|
|
{
|
|
hasDot = true;
|
|
_index++;
|
|
while (_index < _text.Length && char.IsDigit(_text[_index]))
|
|
{
|
|
_index++;
|
|
}
|
|
}
|
|
|
|
if (_index < _text.Length && (_text[_index] == 'e' || _text[_index] == 'E'))
|
|
{
|
|
hasDot = true;
|
|
_index++;
|
|
if (_index < _text.Length && (_text[_index] == '+' || _text[_index] == '-'))
|
|
{
|
|
_index++;
|
|
}
|
|
while (_index < _text.Length && char.IsDigit(_text[_index]))
|
|
{
|
|
_index++;
|
|
}
|
|
}
|
|
|
|
string number = _text.Substring(start, _index - start);
|
|
if (!hasDot && long.TryParse(number, NumberStyles.Integer, CultureInfo.InvariantCulture, out long longVal))
|
|
{
|
|
value = longVal;
|
|
return true;
|
|
}
|
|
|
|
if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double dbl))
|
|
{
|
|
value = dbl;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool TryReadLiteral(string literal)
|
|
{
|
|
SkipWhitespace();
|
|
if (_text.Length - _index < literal.Length) return false;
|
|
if (string.Compare(_text, _index, literal, 0, literal.Length, StringComparison.OrdinalIgnoreCase) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
_index += literal.Length;
|
|
return true;
|
|
}
|
|
|
|
private bool TryReadChar(char expected)
|
|
{
|
|
SkipWhitespace();
|
|
if (_index >= _text.Length) return false;
|
|
if (_text[_index] != expected) return false;
|
|
_index++;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|