package CQL::Token;

use strict;
use warnings;
use base qw( Exporter );

=head1 NAME

CQL::Token - class for token objects returned by CQL::Lexer

=head1 SYNOPSIS

    my $token = $lexer->nextToken();
    
    if ( $token->type() == CQL_WORD ) { 
        print "the token is a word with value=", $token->string(), "\n";
    }

=head1 DESCRIPTION

Ordinarily you won't really care about the tokens returned by the 
CQL::Lexer since the lexer is used behind the scenes by CQL::Parser.

=head1 METHODS

=head2 new()

    my $token = CQL::Token->new( '=' );

=cut

## CQL keyword types
use constant CQL_LT        => 100;    ## The "<" relation
use constant CQL_GT        => 101;    ## The ">" relation
use constant CQL_EQ        => 102;    ## The "=" relation
use constant CQL_LE        => 103;    ## The "<=" relation
use constant CQL_GE        => 104;    ## The ">=" relation
use constant CQL_NE        => 105;    ## The "<>" relation
use constant CQL_AND       => 106;    ## The "and" boolean
use constant CQL_OR        => 107;    ## The "or" boolean
use constant CQL_NOT       => 108;    ## The "not" boolean
use constant CQL_PROX      => 109;    ## The "prox" boolean
use constant CQL_ANY       => 110;    ## The "any" relation
use constant CQL_ALL       => 111;    ## The "all" relation
use constant CQL_EXACT     => 112;    ## The "exact" relation
use constant CQL_WITHIN    => 113;    ## The "within" relation
use constant CQL_ENCLOSES  => 114;    ## The "encloses" relation
use constant CQL_PARTIAL   => 115;    ## The "partial" relation
use constant CQL_PWORD     => 116;    ## The "word" proximity unit and the "word" relation modifier
use constant CQL_SENTENCE  => 117;    ## The "sentence" proximity unit
use constant CQL_PARAGRAPH => 118;    ## The "paragraph" proximity unit
use constant CQL_ELEMENT   => 119;    ## The "element" proximity unit
use constant CQL_ORDERED   => 120;    ## The "ordered" proximity ordering
use constant CQL_UNORDERED => 121;    ## The "unordered" proximity ordering
use constant CQL_RELEVANT  => 122;    ## The "relevant" relation modifier
use constant CQL_FUZZY     => 123;    ## The "fuzzy" relation modifier
use constant CQL_STEM      => 124;    ## The "stem" relation modifier
use constant CQL_SCR       => 125;    ## The server choice relation
use constant CQL_PHONETIC  => 126;    ## The "phonetic" relation modifier
use constant CQL_WORD      => 127;    ## A general word (not an operator) 
use constant CQL_LPAREN    => 128;    ## A left paren
use constant CQL_RPAREN    => 129;    ## A right paren
use constant CQL_EOF       => 130;    ## End of query
use constant CQL_MODIFIER  => 131;    ## Start of modifier '/'
use constant CQL_STRING    => 132;    ## The "string" relation modifier
use constant CQL_ISODATE   => 133;    ## The "isoDate" relation modifier
use constant CQL_NUMBER    => 134;    ## The "number" relation modifier
use constant CQL_URI       => 135;    ## The "uri" relation modifier
use constant CQL_MASKED    => 137;    ## The "masked" relation modifier
use constant CQL_UNMASKED  => 138;    ## The "unmasked" relation modifier
use constant CQL_NWSE      => 139;    ## The "nwse" relation modifier
use constant CQL_DISTANCE  => 140;    ## The "distance" proximity modifier
use constant CQL_UNIT      => 141;    ## The "unit" proximity modifier

## lookup table for easily determining token type
our %lookupTable = (
    '<'          => CQL_LT,
    '>'          => CQL_GT,
    '='          => CQL_EQ,
    '<='         => CQL_LE,
    '>='         => CQL_GE,
    '<>'         => CQL_NE,
    'and'        => CQL_AND,
    'or'         => CQL_OR,
    'not'        => CQL_NOT,
    'prox'       => CQL_PROX,
    'any'        => CQL_ANY,
    'within'     => CQL_WITHIN,
    'encloses'   => CQL_ENCLOSES,
    'partial'    => CQL_PARTIAL,
    'all'        => CQL_ALL,
    'exact'      => CQL_EXACT,
    'word'       => CQL_PWORD,
    'sentence'   => CQL_SENTENCE,
    'paragraph'  => CQL_PARAGRAPH,
    'element'    => CQL_ELEMENT,
    'ordered'    => CQL_ORDERED,
    'unordered'  => CQL_UNORDERED,
    'relevant'   => CQL_RELEVANT,
    'fuzzy'      => CQL_FUZZY,
    'stem'       => CQL_STEM,
    'phonetic'   => CQL_PHONETIC,
    '('          => CQL_LPAREN,
    ')'          => CQL_RPAREN,
    '/'          => CQL_MODIFIER,
    ''           => CQL_EOF,
    'string'     => CQL_STRING,
    'isodate'    => CQL_ISODATE,
    'number'     => CQL_NUMBER,
    'uri'        => CQL_URI,
    'masked'     => CQL_MASKED,
    'unmasked'   => CQL_UNMASKED,
    'nwse'       => CQL_NWSE,
    'distance'   => CQL_DISTANCE,
    'unit'       => CQL_UNIT,
);

## constants available for folks to use when looking at 
## token types

our @EXPORT = qw(
    CQL_LT CQL_GT CQL_EQ CQL_LE CQL_GE CQL_NE CQL_AND CQL_OR CQL_NOT 
    CQL_PROX CQL_ANY CQL_ALL CQL_EXACT CQL_PWORD CQL_SENTENCE CQL_PARAGRAPH
    CQL_ELEMENT CQL_ORDERED CQL_UNORDERED CQL_RELEVANT CQL_FUZZY
    CQL_STEM CQL_SCR CQL_PHONETIC CQL_RPAREN CQL_LPAREN
    CQL_WORD CQL_PHRASE CQL_EOF CQL_MODIFIER CQL_STRING CQL_ISODATE
    CQL_NUMBER CQL_URI CQL_MASKED CQL_UNMASKED CQL_WITHIN CQL_PARTIAL
    CQL_ENCLOSES CQL_NWSE
    CQL_DISTANCE CQL_UNIT
);

=head2 new()

=cut

sub new {
    my ($class,$string) = @_;
    my $type;

    # see if it's a reserved word, which are case insensitive
    my $normalString = lc($string);
    if ( exists($lookupTable{$normalString}) ) {
        $type = $lookupTable{$normalString};
    }
    else {
        $type = CQL_WORD;
        # remove outer quotes if present
        if ($string =~ m/^"(.*)"$/g) {
            $string = $1;
            # replace escaped double quote with double quote. 
            # Is save this way cause the string is assumed to be syntactically correct
            $string =~ s/\\"/"/g;
        }
    }
    return bless { string=>$string, type=>$type }, ref($class) || $class;
}

=head2 getType()

Returns the token type which will be availble as one of the constants
that CQL::Token exports. See internals for a list of available constants.

=cut

sub getType { return shift->{type}; }

=head2 getString()

Retruns the string equivalent of the token. Particularly useful when
you only know it's a CQL_WORD.

=cut

sub getString { return shift->{string}; }

1;
