PageRenderTime 30ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 1ms

/src/NUnit/UiException/CSharpParser/TokenDictionary.cs

#
C# | 286 lines | 126 code | 39 blank | 121 comment | 15 complexity | ff66ece6a958c2a90c8aa771dbf09711 MD5 | raw file
Possible License(s): GPL-2.0
  1. // ****************************************************************
  2. // This is free software licensed under the NUnit license. You may
  3. // obtain a copy of the license at http://nunit.org
  4. // ****************************************************************
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Text;
  8. using System.Collections;
  9. namespace NUnit.UiException.CodeFormatters
  10. {
  11. /// <summary>
  12. /// TokenDictionary is responsible for defining and identifying a set of basic
  13. /// strings in a given text that have a particular meaning. For instance:
  14. /// - Separator, (ex: "{" ";" "]" ...)
  15. /// - comment markers, (ex: "//" "/*" "*/")
  16. /// - string markers, (ex: '"' '\'')
  17. /// - Other -> "Text" (all other strings but the ones aboves).
  18. /// To achieve this, TokenDictionary firstly defines methods to register and query which
  19. /// strings have been registered. Secondly it defines a convenient method: TryMatch()
  20. /// responsible for splitting a given string in one or two parts where the first one will
  21. /// fall in one of the above categories. When calling TryMatch() iteratively --see Lexer--,
  22. /// one can tag a text into a list of tokens that might server for a semantic analysis.
  23. ///
  24. /// TokenDictionary and Lexer are both responsible for dealing with the lexical analysis
  25. /// job that is the first step to make basic syntax coloring.
  26. /// </summary>
  27. /// <see cref="Lexer">Front class for the lexical analysis.</see>
  28. public class TokenDictionary :
  29. IEnumerable
  30. {
  31. private List<InternalLexToken> _list;
  32. private List<LexToken> _working;
  33. /// <summary>
  34. /// Build an empty instance of TokenDictionary.
  35. /// </summary>
  36. public TokenDictionary()
  37. {
  38. _list = new List<InternalLexToken>();
  39. _working = new List<LexToken>();
  40. return;
  41. }
  42. /// <summary>
  43. /// Gets the token count defined in this instance.
  44. /// </summary>
  45. public int Count
  46. {
  47. get { return (_list.Count); }
  48. }
  49. /// <summary>
  50. /// Gets the token at the given index.
  51. /// </summary>
  52. /// <param name="index">Index of the token to be returned.</param>
  53. /// <returns>The token at the specified index.</returns>
  54. public LexToken this[int index]
  55. {
  56. get { return (_list[index]); }
  57. }
  58. /// <summary>
  59. /// Build a new token and add it to the list of tokens known by TokenDictionary.
  60. /// Tokens must be added from the longest text value to the shortest otherwise
  61. /// an exception will be raised.
  62. /// </summary>
  63. /// <param name="value">
  64. /// The token's text value. It must not be null nor empty. It must not be already
  65. /// defined neither. If there are tokens already defined, value's length must not
  66. /// be longer than the previous added token.
  67. /// </param>
  68. /// <param name="tag">The token's tag value.</param>
  69. public void Add(string value, LexerTag tag)
  70. {
  71. InternalLexToken newToken;
  72. UiExceptionHelper.CheckNotNull(value, "value");
  73. UiExceptionHelper.CheckFalse(value == "",
  74. "Token value must not be empty.", "value");
  75. UiExceptionHelper.CheckFalse(
  76. Contains(value),
  77. String.Format("Token '{0}' is already defined.", value),
  78. "value");
  79. if (Count > 0)
  80. UiExceptionHelper.CheckTrue(
  81. _list[Count - 1].Text.Length >= value.Length,
  82. "Tokens must be inserted from the longest to the shortest value.",
  83. "value");
  84. newToken = new InternalLexToken(value, tag);
  85. // loop through each item to populate
  86. // newToken.StartingWith list.
  87. foreach (InternalLexToken item in _list)
  88. if (item.Text.StartsWith(value))
  89. newToken.StartingWith.Add(item);
  90. _list.Add(newToken);
  91. return;
  92. }
  93. /// <summary>
  94. /// Tests whether the given string matches a token known by this instance.
  95. /// </summary>
  96. /// <param name="value">
  97. /// A string to be identify with a token in this instance.
  98. /// </param>
  99. /// <returns>
  100. /// True if the string matches a token's text
  101. /// value in this instance, false otherwise.
  102. /// </returns>
  103. public bool Contains(string value)
  104. {
  105. foreach (LexToken item in _list)
  106. if (item.Text == value)
  107. return (true);
  108. return (false);
  109. }
  110. /// <summary>
  111. /// Try to match in "text" + "prediction" a token previously defined with the Add() method.
  112. /// Since TryMatch() may return null, it should be called from a loop that scans iteratively
  113. /// all characters of an input text.
  114. ///
  115. /// TryMatch() can put the caller in the two following situations:
  116. /// 1) if parameters "text"+"prediction" don't hold any token, null will be returned. In this
  117. /// case caller is expected to append to "text" one character more and to shift "prediction"
  118. /// by one character ahead before calling TryMatch() again.
  119. /// 2) if parameters "text"+"prediction" look like [data]TOKEN --where [data] is any other string
  120. /// but the ones in tokens-- TryMatch() will return an instance of LexToken which LexToken.Text
  121. /// and LexToken.Tag properties will be setup with identified data. In this case caller is
  122. /// expected to shift its reading position by the lenght of text put in LexToken.Text. Besides
  123. /// "text" parameter should reset its length to 1 again.
  124. /// </summary>
  125. /// <param name="text">
  126. /// At the very beginning, text should be of size 1 and set up with the first character from the
  127. /// input text. Each time TryMatch() return null, the following character from the input text
  128. /// should be appended to "text". Once a token is returned, this parameter should reset its size
  129. /// to 1 and be filled with the character coming just after the identified string.
  130. /// This parameter cannot be null.
  131. /// </param>
  132. /// <param name="prediction">
  133. /// This parameter represents a constant sized string that goes just before the data in "text".
  134. /// If the caller reach the end of the text and there are not enough character to fill "prediction"
  135. /// completely this parameter can be filled with remaining character and eventually becoming empty.
  136. /// The size of this string should be equal to the lenght of the longest token defined in
  137. /// this instance of TokenDictionary.
  138. /// This parameter cannot be null.
  139. /// </param>
  140. /// <returns>
  141. /// The first identifiable LexToken in "text"+"prediction". Returns may be null.
  142. /// </returns>
  143. /// <see cref="Lexer.Next()">
  144. /// To have a look on the loop implementation..
  145. /// </see>
  146. public LexToken TryMatch(string text, string prediction)
  147. {
  148. UiExceptionHelper.CheckNotNull(text, "text");
  149. UiExceptionHelper.CheckNotNull(prediction, "prediction");
  150. foreach (InternalLexToken token in _list)
  151. {
  152. if (text.EndsWith(token.Text))
  153. {
  154. // text may look like [data]TOKEN
  155. // where [data] is normal text possibly empty.
  156. if (text.Length > token.Text.Length)
  157. {
  158. // get only [data] part
  159. return (new LexToken(
  160. text.Substring(0, text.Length - token.Text.Length),
  161. LexerTag.Text, -1));
  162. }
  163. // text looks like TOKEN, however we can't return text at
  164. // this stage before testing content of prediction. Since
  165. // there is a possibility that a longer TOKEN be in the concatenated
  166. // string: text + prediction. (note: longer TOKENs have higher
  167. // priority over shorter ones)
  168. if (prediction != "")
  169. {
  170. string pattern;
  171. int i;
  172. _working.Clear();
  173. PopulateTokenStartingWith(token, _working);
  174. for (i = 1; i < _working.Count; ++i)
  175. {
  176. if (_working[i].Text.Length <= text.Length ||
  177. _working[i].Text.Length > text.Length + prediction.Length)
  178. continue;
  179. pattern = text + prediction.Substring(0,
  180. _working[i].Text.Length - text.Length);
  181. if (_working[i].Text == pattern)
  182. return (_working[i]);
  183. }
  184. }
  185. return (token);
  186. }
  187. }
  188. // no match found, if prediction is empty
  189. // this means we reach end of text and return
  190. // text as a LexerToken.Text
  191. if (prediction == "")
  192. return (new LexToken(text, LexerTag.Text, -1));
  193. return (null);
  194. }
  195. /// <summary>
  196. /// Builds the list of all LexToken which text value starts with the one in starter.
  197. /// </summary>
  198. /// <param name="starter">The token that the reference text.</param>
  199. /// <param name="output">The list of tokens which text starts with the one in starter.</param>
  200. protected void PopulateTokenStartingWith(LexToken starter, List<LexToken> output)
  201. {
  202. InternalLexToken token;
  203. UiExceptionHelper.CheckNotNull(starter, "starter");
  204. UiExceptionHelper.CheckNotNull(output, "output");
  205. output.Add(starter);
  206. token = (InternalLexToken)starter;
  207. foreach (LexToken item in token.StartingWith)
  208. output.Add(item);
  209. return;
  210. }
  211. #region InternalLexToken
  212. /// <summary>
  213. /// Inherits of LexToken and add a public array that holds the list of all other tokens
  214. /// which text values start with the one in the current instance.
  215. /// </summary>
  216. class InternalLexToken :
  217. LexToken
  218. {
  219. /// <summary>
  220. /// Holds the list of all other tokens which text values start like the one
  221. /// in this instance. This array is used to solve ambiguity when finding a
  222. /// string that could possibly represents more than one token.
  223. /// </summary>
  224. public List<LexToken> StartingWith;
  225. /// <summary>
  226. /// Build a new instance of InternalLexToken with the given data.
  227. /// </summary>
  228. /// <param name="value">The token's text value.</param>
  229. /// <param name="tag">The token's tag value.</param>
  230. public InternalLexToken(string value, LexerTag tag)
  231. {
  232. _start = -1;
  233. _text = value;
  234. _tag = tag;
  235. StartingWith = new List<LexToken>();
  236. return;
  237. }
  238. }
  239. #endregion
  240. #region IEnumerable Membres
  241. public IEnumerator GetEnumerator()
  242. {
  243. return (_list.GetEnumerator());
  244. }
  245. #endregion
  246. }
  247. }