385 lines
7.8 KiB
C#
Raw Normal View History

//
// SqlWhereClauseTokenizer.cs
//
// Author:
// Juraj Skripsky (juraj@hotfeet.ch)
//
// (C) 2004 HotFeet GmbH (http://www.hotfeet.ch)
//
//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;
using System.Collections;
namespace Mono.Data.SqlExpressions {
internal class Tokenizer : yyParser.yyInput {
private static readonly IDictionary tokenMap = new Hashtable ();
private static readonly Object [] tokens = {
Token.AND, "and",
Token.OR, "or",
Token.NOT, "not",
Token.TRUE, "true",
Token.FALSE, "false",
Token.NULL, "null",
Token.PARENT, "parent",
Token.CHILD, "child",
Token.IS, "is",
Token.IN, "in",
Token.NOT_IN, "not in",
Token.LIKE, "like",
Token.NOT_LIKE, "not like",
Token.COUNT, "count",
Token.SUM, "sum",
Token.AVG, "avg",
Token.MAX, "max",
Token.MIN, "min",
Token.STDEV, "stdev",
Token.VAR, "var",
Token.IIF, "iif",
Token.SUBSTRING, "substring",
Token.ISNULL, "isnull",
Token.LEN, "len",
Token.TRIM, "trim",
Token.CONVERT, "convert"
};
private char[] input;
private int pos;
private int tok;
private object val;
static Tokenizer ()
{
for (int i = 0; i < tokens.Length; i += 2)
tokenMap.Add (tokens [i + 1], tokens [i]);
}
public Tokenizer (string strInput)
{
input = strInput.ToCharArray ();
pos = 0;
}
private char Current() {
return input [pos];
}
private char Next() {
if (pos + 1 >= input.Length)
return (char)0;
return input [pos + 1];
}
private bool MoveNext() {
pos++;
if (pos >= input.Length)
return false;
return true;
}
private bool SkipWhiteSpace ()
{
if (pos >= input.Length)
return false;
while (Char.IsWhiteSpace (Current ())) {
if (!MoveNext ())
return false;
}
return true;
}
private object ReadNumber ()
{
StringBuilder sb = new StringBuilder ();
sb.Append (Current ());
char next;
while (Char.IsDigit (next = Next ()) || next == '.') {
sb.Append (next);
if (!MoveNext ())
break;
}
string str = sb.ToString ();
if (str.IndexOf ('.') < 0)
return Int64.Parse (str, CultureInfo.InvariantCulture);
return double.Parse (str, CultureInfo.InvariantCulture);
}
private char ProcessEscapes(char c)
{
if (c == '\\') {
if (MoveNext())
c = Current ();
else
c = '\0';
switch (c) {
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case '\\':
c = '\\';
break;
default:
throw new SyntaxErrorException (String.Format ("Invalid escape sequence: '\\{0}'.", c));
}
}
return c;
}
private string ReadString (char terminator)
{
return ReadString (terminator, false /* canEscape */);
}
private string ReadString (char terminator,
bool canEscape // twice the terminator is not a terminator
)
{
bool terminated = false;
StringBuilder sb = new StringBuilder ();
while (MoveNext ()) {
if (Current () == terminator) {
if (Next () == terminator) {
sb.Append (ProcessEscapes (Current ()));
MoveNext ();
continue;
}
terminated = true;
break;
}
sb.Append (ProcessEscapes (Current ()));
}
if (! terminated)
throw new SyntaxErrorException (String.Format ("invalid string at {0}{1}<--",
terminator,
sb.ToString ())
);
return sb.ToString ();
}
private string ReadIdentifier ()
{
StringBuilder sb = new StringBuilder ();
sb.Append (Current ());
char next;
string ret;
while ((next = Next ()) == '_' || Char.IsLetterOrDigit (next) || next == '\\') {
sb.Append (ProcessEscapes (next));
if (!MoveNext ())
break;
}
ret = sb.ToString ();
if (String.Compare (ret,
"not",
#if NET_2_0
StringComparison.OrdinalIgnoreCase
#else
true, CultureInfo.InvariantCulture
#endif
) == 0) {
int savedPos = pos;
while (Char.IsWhiteSpace ((next = Next ()))) {
if (!MoveNext ()) {
pos = savedPos;
return ret;
}
}
MoveNext ();
string target;
switch (Current ()) {
case 'i':
case 'I':
target = "in";
break;
case 'l':
case 'L':
target = "like";
break;
default:
pos = savedPos;
return ret;
}
int tlen = target.Length;
int idx = 1;
while (tlen-- > 0 && Char.IsLetter ((next = Next ()))) {
if (target [idx++] != Char.ToLowerInvariant (next)) {
pos = savedPos;
return ret;
}
MoveNext ();
}
sb.Append (' ');
sb.Append (target);
ret = sb.ToString ();
}
return ret;
}
private int ParseIdentifier ()
{
string strToken = ReadIdentifier ();
object tokenObj = tokenMap[strToken.ToLower()];
if(tokenObj != null)
return (int)tokenObj;
val = strToken;
return Token.Identifier;
}
private int ParseToken ()
{
char cur;
switch (cur = Current ()) {
case '(':
return Token.PAROPEN;
case ')':
return Token.PARCLOSE;
case '.':
return Token.DOT;
case ',':
return Token.COMMA;
case '+':
return Token.PLUS;
case '-':
return Token.MINUS;
case '*':
return Token.MUL;
case '/':
return Token.DIV;
case '%':
return Token.MOD;
case '=':
return Token.EQ;
case '<':
return Token.LT;
case '>':
return Token.GT;
case '[':
val = ReadString (']');
return Token.Identifier;
case '#':
string date = ReadString ('#');
val = DateTime.Parse (date, CultureInfo.InvariantCulture);
return Token.DateLiteral;
case '\'':
case '\"':
val = ReadString (cur, true);
return Token.StringLiteral;
default:
if (Char.IsDigit (cur)) {
val = ReadNumber ();
return Token.NumberLiteral;
} else if (Char.IsLetter (cur) || cur == '_')
return ParseIdentifier ();
break;
}
throw new SyntaxErrorException ("invalid token: '" + cur + "'");
}
///////////////////////////
// yyParser.yyInput methods
///////////////////////////
/** move on to next token.
@return false if positioned beyond tokens.
@throws IOException on input error.
*/
public bool advance ()
{
if (!SkipWhiteSpace())
return false;
tok = ParseToken();
MoveNext ();
return true;
}
/** classifies current token.
Should not be called if advance() returned false.
@return current %token or single character.
*/
public int token ()
{
return tok;
}
/** associated with current token.
Should not be called if advance() returned false.
@return value for token().
*/
public Object value ()
{
return val;
}
}
}