pgLabII/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringTokenizer.cs

131 lines
3.5 KiB
C#
Raw Normal View History

using System.Text;
using FluentResults;
using static System.Net.Mime.MediaTypeNames;
namespace pgLabII.PgUtils.ConnectionStrings;
public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
{
private readonly string input;
private int position = 0;
public bool IsEof
{
get
{
ConsumeWhitespace();
return position >= input.Length;
}
}
public PqConnectionStringTokenizer(string input)
{
this.input = input;
position = 0;
}
public Result<string> GetKeyword()
{
if (IsEof)
return Result.Fail($"Unexpected end of file was expecting a keyword at position {position}");
return GetString(forKeyword: true);
}
public Result ConsumeEquals()
{
ConsumeWhitespace();
if (position < input.Length && input[position] == '=')
{
position++;
return Result.Ok();
}
else
return Result.Fail($"Was expecting '=' after keyword at position {position}");
}
public Result<string> GetValue()
{
if (IsEof)
return Result.Fail($"Unexpected end of file was expecting a keyword at position {position}");
return GetString(forKeyword: false);
}
private Result<string> GetString(bool forKeyword)
{
if (forKeyword && input[position] == '=')
return Result.Fail($"Unexpected '=' was expecting keyword at position {position}");
if (input[position] == '\'')
return ParseQuotedText();
return UnquotedString(forKeyword);
}
private void ConsumeWhitespace()
{
while (position < input.Length && char.IsWhiteSpace(input[position]))
position++;
}
private string UnquotedString(bool forKeyword)
{
int start = position;
while (++position < input.Length)
{
char c = input[position];
// Libpq syntax does not use semicolons as pair separators; treat ';' as invalid here
if (c == ';')
{
// Force tokenizer to stop and later cause a parse error by making GetValue/keyword incomplete
break;
}
if (char.IsWhiteSpace(c)) break;
if (forKeyword && c == '=') break;
}
return input.Substring(start, position - start);
}
private Result<string> ParseQuotedText()
{
bool escape = false;
StringBuilder sb = new();
int start = position;
while (++position < input.Length)
{
char c = input[position];
if (escape)
{
switch (c)
{
case '\'':
case '\\':
sb.Append(c);
escape = false;
break;
default:
return Result.Fail($"Invalid escape sequence at position {position}");
}
}
else
{
if (c == '\'')
{
++position;
return sb.ToString();
}
else if (c == '\\')
{
escape = true;
}
else
{
sb.Append(c);
}
}
}
return Result.Fail($"Missing end quote on value starting at {start}");
}
}