The Labs \ Source Viewer \ SSCLI \ System.Xml.Xsl.XPath \ LexKind

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XPathScanner.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. using System.Diagnostics;
  16. //using System.Globalization;
  17. //using System.Text;
  18. namespace System.Xml.Xsl.XPath
  19. {
  20.     using Res = System.Xml.Utils.Res;
  21.    
  22.     internal enum LexKind
  23.     {
  24.         Comma = ',',
  25.         Slash = '/',
  26.         At = '@',
  27.         Dot = '.',
  28.         LParens = '(',
  29.         RParens = ')',
  30.         LBracket = '[',
  31.         RBracket = ']',
  32.         LBrace = '{',
  33.         RBrace = '}',
  34.         Star = '*',
  35.         Plus = '+',
  36.         Minus = '-',
  37.         Eq = '=',
  38.         Lt = '<',
  39.         Gt = '>',
  40.         Bang = '!',
  41.         Dollar = '$',
  42.         Union = '|',
  43.        
  44.         Ne = 'N',
  45.         // !=
  46.         Le = 'L',
  47.         // <=
  48.         Ge = 'G',
  49.         // >=
  50.         DotDot = 'D',
  51.         // ..
  52.         SlashSlash = 'S',
  53.         // //
  54.         Name = 'n',
  55.         // Name
  56.         String = 's',
  57.         // String literal
  58.         Number = 'd',
  59.         // Numeric literal
  60.         Axis = 'a',
  61.         // Axis
  62.         Unknown = 'U',
  63.         // Unknown char
  64.         Eof = 'E'
  65.     }
  66.    
  67.     internal sealed class XPathScanner
  68.     {
  69.         private string xpathExpr;
  70.         private int curIndex;
  71.         private char curChar;
  72.         private LexKind kind;
  73.         private string name;
  74.         private string prefix;
  75.         private string stringValue;
  76.         private double numberValue = double.NaN;
  77.         private bool canBeFunction;
  78.         private int lexStart;
  79.         private int prevLexEnd;
  80.        
  81.         private XmlCharType xmlCharType = XmlCharType.Instance;
  82.        
  83.         public XPathScanner(string xpathExpr) : this(xpathExpr, 0)
  84.         {
  85.         }
  86.        
  87.         public XPathScanner(string xpathExpr, int startFrom)
  88.         {
  89.             Debug.Assert(xpathExpr != null);
  90.             this.xpathExpr = xpathExpr;
  91.             SetSourceIndex(startFrom);
  92.             NextLex();
  93.         }
  94.        
  95.         public string Source {
  96.             get { return xpathExpr; }
  97.         }
  98.         public LexKind Kind {
  99.             get { return kind; }
  100.         }
  101.         public int LexStart {
  102.             get { return lexStart; }
  103.         }
  104.         public int LexSize {
  105.             get { return curIndex - lexStart; }
  106.         }
  107.         public int PrevLexEnd {
  108.             get { return prevLexEnd; }
  109.         }
  110.        
  111.         private void SetSourceIndex(int index)
  112.         {
  113.             curIndex = index - 1;
  114.             NextChar();
  115.         }
  116.        
  117.         private bool NextChar()
  118.         {
  119.             Debug.Assert(-1 <= curIndex && curIndex < xpathExpr.Length);
  120.             curIndex++;
  121.             if (curIndex < xpathExpr.Length) {
  122.                 curChar = xpathExpr[curIndex];
  123.                 return true;
  124.             }
  125.             else {
  126.                 Debug.Assert(curIndex == xpathExpr.Length);
  127.                 curChar = '\0';
  128.                 return false;
  129.             }
  130.         }
  131.        
  132.         public string Name {
  133.             get {
  134.                 Debug.Assert(kind == LexKind.Name || kind == LexKind.Axis);
  135.                 Debug.Assert(name != null);
  136.                 return name;
  137.             }
  138.         }
  139.        
  140.         public string Prefix {
  141.             get {
  142.                 Debug.Assert(kind == LexKind.Name);
  143.                 Debug.Assert(prefix != null);
  144.                 return prefix;
  145.             }
  146.         }
  147.        
  148.         public bool IsKeyword(string keyword)
  149.         {
  150.             return (kind == LexKind.Name && prefix.Length == 0 && name.Equals(keyword));
  151.         }
  152.        
  153.         public string RawValue {
  154.             get {
  155.                 if (kind == LexKind.Eof) {
  156.                     return LexKindToString(kind);
  157.                 }
  158.                 else {
  159.                     return xpathExpr.Substring(lexStart, curIndex - lexStart);
  160.                 }
  161.             }
  162.         }
  163.        
  164.         public string StringValue {
  165.             get {
  166.                 Debug.Assert(kind == LexKind.String);
  167.                 Debug.Assert(stringValue != null);
  168.                 return stringValue;
  169.             }
  170.         }
  171.        
  172.         public double NumberValue {
  173.             get {
  174.                 Debug.Assert(kind == LexKind.Number);
  175.                 Debug.Assert(numberValue != double.NaN);
  176.                 return numberValue;
  177.             }
  178.         }
  179.        
  180.         // To parse PathExpr we need a way to distinct name from function.
  181.         // THis distinction can't be done without context: "or (1 != 0)" this this a function or 'or' in OrExp
  182.         public bool CanBeFunction {
  183.             get {
  184.                 Debug.Assert(kind == LexKind.Name);
  185.                 return canBeFunction;
  186.             }
  187.         }
  188.        
  189.         void SkipSpace()
  190.         {
  191.             while (xmlCharType.IsWhiteSpace(curChar) && NextChar()) {
  192.             }
  193.         }
  194.        
  195.         public bool NextLex()
  196.         {
  197.             prevLexEnd = curIndex;
  198.             SkipSpace();
  199.             lexStart = curIndex;
  200.             switch (curChar) {
  201.                 case '\0':
  202.                     kind = LexKind.Eof;
  203.                     return false;
  204.                 case ',':
  205.                 case '@':
  206.                 case '(':
  207.                 case ')':
  208.                 case '|':
  209.                 case '*':
  210.                 case '[':
  211.                 case ']':
  212.                 case '+':
  213.                 case '-':
  214.                 case '=':
  215.                 case '#':
  216.                 case '$':
  217.                 case '{':
  218.                 case '}':
  219.                     kind = (LexKind)curChar;
  220.                     NextChar();
  221.                     break;
  222.                 case '<':
  223.                     kind = LexKind.Lt;
  224.                     NextChar();
  225.                     if (curChar == '=') {
  226.                         kind = LexKind.Le;
  227.                         NextChar();
  228.                     }
  229.                     break;
  230.                 case '>':
  231.                     kind = LexKind.Gt;
  232.                     NextChar();
  233.                     if (curChar == '=') {
  234.                         kind = LexKind.Ge;
  235.                         NextChar();
  236.                     }
  237.                     break;
  238.                 case '!':
  239.                     kind = LexKind.Bang;
  240.                     NextChar();
  241.                     if (curChar == '=') {
  242.                         kind = LexKind.Ne;
  243.                         NextChar();
  244.                     }
  245.                     break;
  246.                 case '.':
  247.                     kind = LexKind.Dot;
  248.                     NextChar();
  249.                     if (curChar == '.') {
  250.                         kind = LexKind.DotDot;
  251.                         NextChar();
  252.                     }
  253.                     else if (xmlCharType.IsDigit(curChar)) {
  254.                         ScanFraction();
  255.                     }
  256.                     break;
  257.                 case '/':
  258.                     kind = LexKind.Slash;
  259.                     NextChar();
  260.                     if (curChar == '/') {
  261.                         kind = LexKind.SlashSlash;
  262.                         NextChar();
  263.                     }
  264.                     break;
  265.                 case '"':
  266.                 case '\'':
  267.                     ScanString();
  268.                     break;
  269.                 default:
  270.                     if (xmlCharType.IsDigit(curChar)) {
  271.                         ScanNumber();
  272.                     }
  273.                     else if (xmlCharType.IsStartNCNameChar(curChar)) {
  274.                         kind = LexKind.Name;
  275.                         this.name = ScanNCName();
  276.                         this.prefix = string.Empty;
  277.                         int saveSourceIndex = curIndex;
  278.                         // "foo:bar" is one lexem not three because it doesn't allow spaces in between
  279.                         // We should distinct it from "foo::" and need process "foo ::" as well
  280.                         if (curChar == ':') {
  281.                             NextChar();
  282.                             // can be "foo:bar" or "foo::"
  283.                             if (curChar == ':') {
  284.                                 // "foo::"
  285.                                 NextChar();
  286.                                 kind = LexKind.Axis;
  287.                             }
  288.                             else {
  289.                                 // "foo:*", "foo:bar" or "foo: "
  290.                                 if (curChar == '*') {
  291.                                     NextChar();
  292.                                     this.prefix = this.name;
  293.                                     this.name = "*";
  294.                                 }
  295.                                 else if (xmlCharType.IsStartNCNameChar(curChar)) {
  296.                                     this.prefix = this.name;
  297.                                     this.name = ScanNCName();
  298.                                 }
  299.                                 else {
  300.                                     // this lex is something like "foo:?". Let's it be recognized as name "foo"
  301.                                     // and leave ":-" to be scaned late as unknown lex.
  302.                                     SetSourceIndex(saveSourceIndex);
  303.                                 }
  304.                             }
  305.                         }
  306.                         else {
  307.                             SkipSpace();
  308.                             if (curChar == ':') {
  309.                                 NextChar();
  310.                                 // it can be "foo ::" or just "foo :"
  311.                                 if (curChar == ':') {
  312.                                     NextChar();
  313.                                     kind = LexKind.Axis;
  314.                                 }
  315.                                 else {
  316.                                     // this lex is something like "foo :?". Let's it be recognized as name "foo"
  317.                                     // and leave ":-" to be scaned late as unknown lex.
  318.                                     SetSourceIndex(saveSourceIndex);
  319.                                 }
  320.                             }
  321.                         }
  322.                         // look ahead for '('. I don't want curIndex to be moved by SkipSpace() here to be able to detect presize lexem size.
  323.                         saveSourceIndex = curIndex;
  324.                         SkipSpace();
  325.                         this.canBeFunction = (curChar == '(');
  326.                         SetSourceIndex(saveSourceIndex);
  327.                     }
  328.                     else {
  329.                         kind = LexKind.Unknown;
  330.                         NextChar();
  331.                     }
  332.                     break;
  333.             }
  334.             return true;
  335.         }
  336.        
  337.         private void ScanNumber()
  338.         {
  339.             Debug.Assert(xmlCharType.IsDigit(curChar));
  340.             int start = curIndex;
  341.             while (xmlCharType.IsDigit(curChar)) {
  342.                 NextChar();
  343.             }
  344.             if (curChar == '.') {
  345.                 NextChar();
  346.                 while (xmlCharType.IsDigit(curChar)) {
  347.                     NextChar();
  348.                 }
  349.             }
  350.             if ((curChar & (~32)) == 'E') {
  351.                 NextChar();
  352.                 if (curChar == '+' || curChar == '-') {
  353.                     NextChar();
  354.                 }
  355.                 while (xmlCharType.IsDigit(curChar)) {
  356.                     NextChar();
  357.                 }
  358.                 throw CreateException(Res.XPath_ScientificNotation);
  359.             }
  360.             this.kind = LexKind.Number;
  361.             this.numberValue = XPathConvert.StringToDouble(xpathExpr.Substring(start, curIndex - start));
  362.         }
  363.        
  364.         private void ScanFraction()
  365.         {
  366.             Debug.Assert(xmlCharType.IsDigit(curChar));
  367.             int start = curIndex - 1;
  368.             Debug.Assert(0 <= start && xpathExpr[start] == '.');
  369.             while (xmlCharType.IsDigit(curChar)) {
  370.                 NextChar();
  371.             }
  372.             this.kind = LexKind.Number;
  373.             this.numberValue = XPathConvert.StringToDouble(xpathExpr.Substring(start, curIndex - start));
  374.         }
  375.        
  376.         private void ScanString()
  377.         {
  378.             char endChar = curChar;
  379.             int start = curIndex + 1;
  380.            
  381.             do {
  382.                 if (!NextChar()) {
  383.                     throw CreateException(Res.XPath_UnclosedString);
  384.                 }
  385.             }
  386.             while (curChar != endChar);
  387.            
  388.             this.kind = LexKind.String;
  389.             this.stringValue = xpathExpr.Substring(start, curIndex - start);
  390.             NextChar();
  391.         }
  392.        
  393.         private string ScanNCName()
  394.         {
  395.             Debug.Assert(xmlCharType.IsStartNCNameChar(curChar));
  396.             int start = curIndex;
  397.             while (xmlCharType.IsNCNameChar(curChar)) {
  398.                 NextChar();
  399.             }
  400.             return xpathExpr.Substring(start, curIndex - start);
  401.         }
  402.        
  403.         public void PassToken(LexKind t)
  404.         {
  405.             CheckToken(t);
  406.             NextLex();
  407.         }
  408.        
  409.         public void CheckToken(LexKind t)
  410.         {
  411.             if (kind != t) {
  412.                 if (t == LexKind.Eof) {
  413.                     throw CreateException(Res.XPath_EofExpected, RawValue);
  414.                 }
  415.                 else {
  416.                     throw CreateException(Res.XPath_TokenExpected, LexKindToString(t), RawValue);
  417.                 }
  418.             }
  419.         }
  420.        
  421.         public string LexKindToString(LexKind t)
  422.         {
  423.             const string OneCharLexemes = ",/@.()[]{}*+-=<>!$|";
  424.            
  425.             if (OneCharLexemes.IndexOf((char)t) >= 0) {
  426.                 return ((char)t).ToString();
  427.             }
  428.            
  429.             switch (t) {
  430.                 case LexKind.Ne:
  431.                     return "!=";
  432.                 case LexKind.Le:
  433.                     return "<=";
  434.                 case LexKind.Ge:
  435.                     return ">=";
  436.                 case LexKind.DotDot:
  437.                     return "..";
  438.                 case LexKind.SlashSlash:
  439.                     return "//";
  440.                 case LexKind.Name:
  441.                     return "<name>";
  442.                 case LexKind.String:
  443.                     return "<string literal>";
  444.                 case LexKind.Number:
  445.                     return "<number literal>";
  446.                 case LexKind.Axis:
  447.                     return "<axis>";
  448.                 case LexKind.Unknown:
  449.                     return "<unknown>";
  450.                 case LexKind.Eof:
  451.                     return "<eof>";
  452.                 default:
  453.                     Debug.Fail("Must not get here");
  454.                     return string.Empty;
  455.             }
  456.         }
  457.        
  458.         public XPathCompileException CreateException(string resId, params string[] args)
  459.         {
  460.             return new XPathCompileException(xpathExpr, lexStart, curIndex, resId, args);
  461.         }
  462.     }
  463. }

Developer Fusion