Generalize and fix command parsing.
parent
293286a767
commit
1781ce29df
@ -0,0 +1,233 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Data.Jet
|
||||
{
|
||||
public class JetCommandParser
|
||||
{
|
||||
public string SqlFragment { get; }
|
||||
public char[] States { get; }
|
||||
|
||||
public JetCommandParser(string sqlFragment)
|
||||
{
|
||||
SqlFragment = sqlFragment ?? throw new ArgumentNullException(nameof(sqlFragment));
|
||||
States = new char[sqlFragment.Length];
|
||||
|
||||
Parse();
|
||||
}
|
||||
|
||||
public IReadOnlyList<int> GetStateIndices(char state, int start = 0, int length = -1)
|
||||
{
|
||||
if (start < 0 ||
|
||||
start >= States.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
}
|
||||
|
||||
if (length < 0 ||
|
||||
length > 0 && start + length > States.Length)
|
||||
{
|
||||
length = States.Length - start;
|
||||
}
|
||||
|
||||
var stateIndices = new List<int>();
|
||||
char? lastState = null;
|
||||
|
||||
for (var i = start; i < length; i++)
|
||||
{
|
||||
var currentState = States[i];
|
||||
|
||||
if (currentState == state &&
|
||||
currentState != lastState)
|
||||
{
|
||||
stateIndices.Add(i);
|
||||
}
|
||||
|
||||
lastState = currentState;
|
||||
}
|
||||
|
||||
return stateIndices.AsReadOnly();
|
||||
}
|
||||
|
||||
public IReadOnlyList<int> GetStateIndices(char[] states, int start = 0, int length = -1)
|
||||
{
|
||||
if (states == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(states));
|
||||
}
|
||||
|
||||
if (states.Length <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(states));
|
||||
}
|
||||
|
||||
if (start < 0 ||
|
||||
start >= States.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
}
|
||||
|
||||
if (length < 0 ||
|
||||
length > 0 && start + length > States.Length)
|
||||
{
|
||||
length = States.Length - start;
|
||||
}
|
||||
|
||||
var stateIndices = new List<int>();
|
||||
char? lastState = null;
|
||||
|
||||
for (var i = start; i < length; i++)
|
||||
{
|
||||
var currentState = States[i];
|
||||
|
||||
if (currentState != lastState &&
|
||||
states.Contains(currentState))
|
||||
{
|
||||
stateIndices.Add(i);
|
||||
}
|
||||
|
||||
lastState = currentState;
|
||||
}
|
||||
|
||||
return stateIndices.AsReadOnly();
|
||||
}
|
||||
|
||||
protected virtual void Parse()
|
||||
{
|
||||
// We use '\0' as the default state and char.
|
||||
var state = '\0';
|
||||
var lastChar = '\0';
|
||||
|
||||
// State machine to parse ODBC and OleDB SQL.
|
||||
for (var i = 0; i < SqlFragment.Length; i++)
|
||||
{
|
||||
var c = SqlFragment[i];
|
||||
|
||||
if (state == '\'')
|
||||
{
|
||||
// We are currently inside a string, or closed the string in the last iteration but didn't
|
||||
// know that at the time, because it still could have been the beginning of an escape sequence.
|
||||
|
||||
if (c == '\'')
|
||||
{
|
||||
// We either end the string, begin an escape sequence or end an escape sequence.
|
||||
if (lastChar == '\'')
|
||||
{
|
||||
// This is the end of an escape sequence.
|
||||
// We continue being in a string.
|
||||
lastChar = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is either the beginning of an escape sequence, or the end of the string.
|
||||
// We will know the in the next iteration.
|
||||
lastChar = '\'';
|
||||
}
|
||||
}
|
||||
else if (lastChar == '\'')
|
||||
{
|
||||
// The last iteration was the end of a string.
|
||||
// Reset the current state and continue processing the current char.
|
||||
state = '\0';
|
||||
lastChar = '\0';
|
||||
States[i - 1] = state;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == '"')
|
||||
{
|
||||
// We are currently inside a string, or closed the string in the last iteration but didn't
|
||||
// know that at the time, because it still could have been the beginning of an escape sequence.
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
// We either end the string, begin an escape sequence or end an escape sequence.
|
||||
if (lastChar == '"')
|
||||
{
|
||||
// This is the end of an escape sequence.
|
||||
// We continue being in a string.
|
||||
lastChar = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is either the beginning of an escape sequence, or the end of the string.
|
||||
// We will know the in the next iteration.
|
||||
lastChar = '"';
|
||||
}
|
||||
}
|
||||
else if (lastChar == '"')
|
||||
{
|
||||
// The last iteration was the end of a string.
|
||||
// Reset the current state and continue processing the current char.
|
||||
state = '\0';
|
||||
lastChar = '\0';
|
||||
States[i - 1] = state;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == '\0')
|
||||
{
|
||||
if (c == '"')
|
||||
{
|
||||
state = '"';
|
||||
}
|
||||
else if (c == '\'')
|
||||
{
|
||||
state = '\'';
|
||||
}
|
||||
else if (c == '`')
|
||||
{
|
||||
state = '`';
|
||||
}
|
||||
else if (c == '?')
|
||||
{
|
||||
States[i] = '?';
|
||||
}
|
||||
else if (c == '@')
|
||||
{
|
||||
// This is either the beginning of a named parameter, or a global variable like @@identity.
|
||||
state = '@';
|
||||
}
|
||||
else if (c == ';')
|
||||
{
|
||||
States[i] = ';';
|
||||
}
|
||||
}
|
||||
else if (state == '`' &&
|
||||
c == '`')
|
||||
{
|
||||
state = '\0';
|
||||
}
|
||||
else if (state == '@')
|
||||
{
|
||||
if (c == '@')
|
||||
{
|
||||
// This has not been a named parameter, but a global variable.
|
||||
// We use '$' to signal a global variable like @@identity.
|
||||
States[i - 1] = '$';
|
||||
}
|
||||
|
||||
state = '\0';
|
||||
}
|
||||
|
||||
if (state != '\0')
|
||||
{
|
||||
States[i] = state;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Handle still pending states:
|
||||
//
|
||||
|
||||
if (state == '\'' && lastChar == '\'' ||
|
||||
state == '"' && lastChar == '"')
|
||||
{
|
||||
// The last iteration was the end of a string.
|
||||
state = '\0';
|
||||
lastChar = '\0';
|
||||
States[States.Length - 1] = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue