Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkCompiler / StringParser / StringParser.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Diagnostics;
  5. using System.Text;
  6. namespace Ink
  7. {
  8. public class StringParser
  9. {
  10. public delegate object ParseRule();
  11. public delegate T SpecificParseRule<T>() where T : class;
  12. public delegate void ErrorHandler(string message, int index, int lineIndex, bool isWarning);
  13. public StringParser (string str)
  14. {
  15. str = PreProcessInputString (str);
  16. state = new StringParserState();
  17. if (str != null) {
  18. _chars = str.ToCharArray ();
  19. } else {
  20. _chars = new char[0];
  21. }
  22. inputString = str;
  23. }
  24. public class ParseSuccessStruct {};
  25. public static ParseSuccessStruct ParseSuccess = new ParseSuccessStruct();
  26. public static CharacterSet numbersCharacterSet = new CharacterSet("0123456789");
  27. protected ErrorHandler errorHandler { get; set; }
  28. public char currentCharacter
  29. {
  30. get
  31. {
  32. if (index >= 0 && remainingLength > 0) {
  33. return _chars [index];
  34. } else {
  35. return (char)0;
  36. }
  37. }
  38. }
  39. public StringParserState state { get; private set; }
  40. public bool hadError { get; protected set; }
  41. // Don't do anything by default, but provide ability for subclasses
  42. // to manipulate the string before it's used as input (converted to a char array)
  43. protected virtual string PreProcessInputString(string str)
  44. {
  45. return str;
  46. }
  47. //--------------------------------
  48. // Parse state
  49. //--------------------------------
  50. protected int BeginRule()
  51. {
  52. return state.Push ();
  53. }
  54. protected object FailRule(int expectedRuleId)
  55. {
  56. state.Pop (expectedRuleId);
  57. return null;
  58. }
  59. protected void CancelRule(int expectedRuleId)
  60. {
  61. state.Pop (expectedRuleId);
  62. }
  63. protected object SucceedRule(int expectedRuleId, object result = null)
  64. {
  65. // Get state at point where this rule stared evaluating
  66. var stateAtSucceedRule = state.Peek(expectedRuleId);
  67. var stateAtBeginRule = state.PeekPenultimate ();
  68. // Allow subclass to receive callback
  69. RuleDidSucceed (result, stateAtBeginRule, stateAtSucceedRule);
  70. // Flatten state stack so that we maintain the same values,
  71. // but remove one level in the stack.
  72. state.Squash();
  73. if (result == null) {
  74. result = ParseSuccess;
  75. }
  76. return result;
  77. }
  78. protected virtual void RuleDidSucceed(object result, StringParserState.Element startState, StringParserState.Element endState)
  79. {
  80. }
  81. protected object Expect(ParseRule rule, string message = null, ParseRule recoveryRule = null)
  82. {
  83. object result = ParseObject(rule);
  84. if (result == null) {
  85. if (message == null) {
  86. message = rule.Method.Name;
  87. }
  88. string butSaw;
  89. string lineRemainder = LineRemainder ();
  90. if (lineRemainder == null || lineRemainder.Length == 0) {
  91. butSaw = "end of line";
  92. } else {
  93. butSaw = "'" + lineRemainder + "'";
  94. }
  95. Error ("Expected "+message+" but saw "+butSaw);
  96. if (recoveryRule != null) {
  97. result = recoveryRule ();
  98. }
  99. }
  100. return result;
  101. }
  102. protected void Error(string message, bool isWarning = false)
  103. {
  104. ErrorOnLine (message, lineIndex + 1, isWarning);
  105. }
  106. protected void ErrorWithParsedObject(string message, Parsed.Object result, bool isWarning = false)
  107. {
  108. ErrorOnLine (message, result.debugMetadata.startLineNumber, isWarning);
  109. }
  110. protected void ErrorOnLine(string message, int lineNumber, bool isWarning)
  111. {
  112. if ( !state.errorReportedAlreadyInScope ) {
  113. var errorType = isWarning ? "Warning" : "Error";
  114. if (errorHandler == null) {
  115. throw new System.Exception (errorType+" on line " + lineNumber + ": " + message);
  116. } else {
  117. errorHandler (message, index, lineNumber-1, isWarning);
  118. }
  119. state.NoteErrorReported ();
  120. }
  121. if( !isWarning )
  122. hadError = true;
  123. }
  124. protected void Warning(string message)
  125. {
  126. Error(message, isWarning:true);
  127. }
  128. public bool endOfInput
  129. {
  130. get { return index >= _chars.Length; }
  131. }
  132. public string remainingString
  133. {
  134. get {
  135. return new string(_chars, index, remainingLength);
  136. }
  137. }
  138. public string LineRemainder()
  139. {
  140. return (string) Peek (() => ParseUntilCharactersFromString ("\n\r"));
  141. }
  142. public int remainingLength
  143. {
  144. get {
  145. return _chars.Length - index;
  146. }
  147. }
  148. public string inputString { get; private set; }
  149. public int lineIndex
  150. {
  151. set {
  152. state.lineIndex = value;
  153. }
  154. get {
  155. return state.lineIndex;
  156. }
  157. }
  158. public int characterInLineIndex
  159. {
  160. set {
  161. state.characterInLineIndex = value;
  162. }
  163. get {
  164. return state.characterInLineIndex;
  165. }
  166. }
  167. public int index
  168. {
  169. // If we want subclass parsers to be able to set the index directly,
  170. // then we would need to know what the lineIndex of the new
  171. // index would be - would we have to step through manually
  172. // counting the newlines to do so?
  173. private set {
  174. state.characterIndex = value;
  175. }
  176. get {
  177. return state.characterIndex;
  178. }
  179. }
  180. public void SetFlag(uint flag, bool trueOrFalse) {
  181. if (trueOrFalse) {
  182. state.customFlags |= flag;
  183. } else {
  184. state.customFlags &= ~flag;
  185. }
  186. }
  187. public bool GetFlag(uint flag) {
  188. return (state.customFlags & flag) != 0;
  189. }
  190. //--------------------------------
  191. // Structuring
  192. //--------------------------------
  193. public object ParseObject(ParseRule rule)
  194. {
  195. int ruleId = BeginRule ();
  196. var stackHeightBefore = state.stackHeight;
  197. var result = rule ();
  198. if (stackHeightBefore != state.stackHeight) {
  199. throw new System.Exception ("Mismatched Begin/Fail/Succeed rules");
  200. }
  201. if (result == null)
  202. return FailRule (ruleId);
  203. SucceedRule (ruleId, result);
  204. return result;
  205. }
  206. public T Parse<T>(SpecificParseRule<T> rule) where T : class
  207. {
  208. int ruleId = BeginRule ();
  209. var result = rule () as T;
  210. if (result == null) {
  211. FailRule (ruleId);
  212. return null;
  213. }
  214. SucceedRule (ruleId, result);
  215. return result;
  216. }
  217. public object OneOf(params ParseRule[] array)
  218. {
  219. foreach (ParseRule rule in array) {
  220. object result = ParseObject(rule);
  221. if (result != null)
  222. return result;
  223. }
  224. return null;
  225. }
  226. public List<object> OneOrMore(ParseRule rule)
  227. {
  228. var results = new List<object> ();
  229. object result = null;
  230. do {
  231. result = ParseObject(rule);
  232. if( result != null ) {
  233. results.Add(result);
  234. }
  235. } while(result != null);
  236. if (results.Count > 0) {
  237. return results;
  238. } else {
  239. return null;
  240. }
  241. }
  242. public ParseRule Optional(ParseRule rule)
  243. {
  244. return () => {
  245. object result = ParseObject(rule);
  246. if( result == null ) {
  247. result = ParseSuccess;
  248. }
  249. return result;
  250. };
  251. }
  252. // Return ParseSuccess instead the real result so that it gets excluded
  253. // from result arrays (e.g. Interleave)
  254. public ParseRule Exclude(ParseRule rule)
  255. {
  256. return () => {
  257. object result = ParseObject(rule);
  258. if( result == null ) {
  259. return null;
  260. }
  261. return ParseSuccess;
  262. };
  263. }
  264. // Combination of both of the above
  265. public ParseRule OptionalExclude(ParseRule rule)
  266. {
  267. return () => {
  268. ParseObject(rule);
  269. return ParseSuccess;
  270. };
  271. }
  272. // Convenience method for creating more readable ParseString rules that can be combined
  273. // in other structuring rules (like OneOf etc)
  274. // e.g. OneOf(String("one"), String("two"))
  275. protected ParseRule String(string str)
  276. {
  277. return () => ParseString (str);
  278. }
  279. private void TryAddResultToList<T>(object result, List<T> list, bool flatten = true)
  280. {
  281. if (result == ParseSuccess) {
  282. return;
  283. }
  284. if (flatten) {
  285. var resultCollection = result as System.Collections.ICollection;
  286. if (resultCollection != null) {
  287. foreach (object obj in resultCollection) {
  288. Debug.Assert (obj is T);
  289. list.Add ((T)obj);
  290. }
  291. return;
  292. }
  293. }
  294. Debug.Assert (result is T);
  295. list.Add ((T)result);
  296. }
  297. public List<T> Interleave<T>(ParseRule ruleA, ParseRule ruleB, ParseRule untilTerminator = null, bool flatten = true)
  298. {
  299. int ruleId = BeginRule ();
  300. var results = new List<T> ();
  301. // First outer padding
  302. var firstA = ParseObject(ruleA);
  303. if (firstA == null) {
  304. return (List<T>) FailRule(ruleId);
  305. } else {
  306. TryAddResultToList(firstA, results, flatten);
  307. }
  308. object lastMainResult = null, outerResult = null;
  309. do {
  310. // "until" condition hit?
  311. if( untilTerminator != null && Peek(untilTerminator) != null ) {
  312. break;
  313. }
  314. // Main inner
  315. lastMainResult = ParseObject(ruleB);
  316. if( lastMainResult == null ) {
  317. break;
  318. } else {
  319. TryAddResultToList(lastMainResult, results, flatten);
  320. }
  321. // Outer result (i.e. last A in ABA)
  322. outerResult = null;
  323. if( lastMainResult != null ) {
  324. outerResult = ParseObject(ruleA);
  325. if (outerResult == null) {
  326. break;
  327. } else {
  328. TryAddResultToList(outerResult, results, flatten);
  329. }
  330. }
  331. // Stop if there are no results, or if both are the placeholder "ParseSuccess" (i.e. Optional success rather than a true value)
  332. } while((lastMainResult != null || outerResult != null)
  333. && !(lastMainResult == ParseSuccess && outerResult == ParseSuccess) && remainingLength > 0);
  334. if (results.Count == 0) {
  335. return (List<T>) FailRule(ruleId);
  336. }
  337. return (List<T>) SucceedRule(ruleId, results);
  338. }
  339. //--------------------------------
  340. // Basic string parsing
  341. //--------------------------------
  342. public string ParseString(string str)
  343. {
  344. if (str.Length > remainingLength) {
  345. return null;
  346. }
  347. int ruleId = BeginRule ();
  348. // Optimisation from profiling:
  349. // Store in temporary local variables
  350. // since they're properties that would have to access
  351. // the rule stack every time otherwise.
  352. int i = index;
  353. int cli = characterInLineIndex;
  354. int li = lineIndex;
  355. bool success = true;
  356. foreach (char c in str) {
  357. if ( _chars[i] != c) {
  358. success = false;
  359. break;
  360. }
  361. if (c == '\n') {
  362. li++;
  363. cli = -1;
  364. }
  365. i++;
  366. cli++;
  367. }
  368. index = i;
  369. characterInLineIndex = cli;
  370. lineIndex = li;
  371. if (success) {
  372. return (string) SucceedRule(ruleId, str);
  373. }
  374. else {
  375. return (string) FailRule (ruleId);
  376. }
  377. }
  378. public char ParseSingleCharacter()
  379. {
  380. if (remainingLength > 0) {
  381. char c = _chars [index];
  382. if (c == '\n') {
  383. lineIndex++;
  384. characterInLineIndex = -1;
  385. }
  386. index++;
  387. characterInLineIndex++;
  388. return c;
  389. } else {
  390. return (char)0;
  391. }
  392. }
  393. public string ParseUntilCharactersFromString(string str, int maxCount = -1)
  394. {
  395. return ParseCharactersFromString(str, false, maxCount);
  396. }
  397. public string ParseUntilCharactersFromCharSet(CharacterSet charSet, int maxCount = -1)
  398. {
  399. return ParseCharactersFromCharSet(charSet, false, maxCount);
  400. }
  401. public string ParseCharactersFromString(string str, int maxCount = -1)
  402. {
  403. return ParseCharactersFromString(str, true, maxCount);
  404. }
  405. public string ParseCharactersFromString(string str, bool shouldIncludeStrChars, int maxCount = -1)
  406. {
  407. return ParseCharactersFromCharSet (new CharacterSet(str), shouldIncludeStrChars);
  408. }
  409. public string ParseCharactersFromCharSet(CharacterSet charSet, bool shouldIncludeChars = true, int maxCount = -1)
  410. {
  411. if (maxCount == -1) {
  412. maxCount = int.MaxValue;
  413. }
  414. int startIndex = index;
  415. // Optimisation from profiling:
  416. // Store in temporary local variables
  417. // since they're properties that would have to access
  418. // the rule stack every time otherwise.
  419. int i = index;
  420. int cli = characterInLineIndex;
  421. int li = lineIndex;
  422. int count = 0;
  423. while ( i < _chars.Length && charSet.Contains (_chars [i]) == shouldIncludeChars && count < maxCount ) {
  424. if (_chars [i] == '\n') {
  425. li++;
  426. cli = -1;
  427. }
  428. i++;
  429. cli++;
  430. count++;
  431. }
  432. index = i;
  433. characterInLineIndex = cli;
  434. lineIndex = li;
  435. int lastCharIndex = index;
  436. if (lastCharIndex > startIndex) {
  437. return new string (_chars, startIndex, index - startIndex);
  438. } else {
  439. return null;
  440. }
  441. }
  442. public object Peek(ParseRule rule)
  443. {
  444. int ruleId = BeginRule ();
  445. object result = rule ();
  446. CancelRule (ruleId);
  447. return result;
  448. }
  449. public string ParseUntil(ParseRule stopRule, CharacterSet pauseCharacters = null, CharacterSet endCharacters = null)
  450. {
  451. int ruleId = BeginRule ();
  452. CharacterSet pauseAndEnd = new CharacterSet ();
  453. if (pauseCharacters != null) {
  454. pauseAndEnd.UnionWith (pauseCharacters);
  455. }
  456. if (endCharacters != null) {
  457. pauseAndEnd.UnionWith (endCharacters);
  458. }
  459. StringBuilder parsedString = new StringBuilder ();
  460. object ruleResultAtPause = null;
  461. // Keep attempting to parse strings up to the pause (and end) points.
  462. // - At each of the pause points, attempt to parse according to the rule
  463. // - When the end point is reached (or EOF), we're done
  464. do {
  465. // TODO: Perhaps if no pause or end characters are passed, we should check *every* character for stopRule?
  466. string partialParsedString = ParseUntilCharactersFromCharSet(pauseAndEnd);
  467. if( partialParsedString != null ) {
  468. parsedString.Append(partialParsedString);
  469. }
  470. // Attempt to run the parse rule at this pause point
  471. ruleResultAtPause = Peek(stopRule);
  472. // Rule completed - we're done
  473. if( ruleResultAtPause != null ) {
  474. break;
  475. } else {
  476. if( endOfInput ) {
  477. break;
  478. }
  479. // Reached a pause point, but rule failed. Step past and continue parsing string
  480. char pauseCharacter = currentCharacter;
  481. if( pauseCharacters != null && pauseCharacters.Contains(pauseCharacter) ) {
  482. parsedString.Append(pauseCharacter);
  483. if( pauseCharacter == '\n' ) {
  484. lineIndex++;
  485. characterInLineIndex = -1;
  486. }
  487. index++;
  488. characterInLineIndex++;
  489. continue;
  490. } else {
  491. break;
  492. }
  493. }
  494. } while(true);
  495. if (parsedString.Length > 0) {
  496. return (string) SucceedRule (ruleId, parsedString.ToString ());
  497. } else {
  498. return (string) FailRule (ruleId);
  499. }
  500. }
  501. // No need to Begin/End rule since we never parse a newline, so keeping oldIndex is good enough
  502. public int? ParseInt()
  503. {
  504. int oldIndex = index;
  505. int oldCharacterInLineIndex = characterInLineIndex;
  506. bool negative = ParseString ("-") != null;
  507. // Optional whitespace
  508. ParseCharactersFromString (" \t");
  509. var parsedString = ParseCharactersFromCharSet (numbersCharacterSet);
  510. if(parsedString == null) {
  511. // Roll back and fail
  512. index = oldIndex;
  513. characterInLineIndex = oldCharacterInLineIndex;
  514. return null;
  515. }
  516. int parsedInt;
  517. if (int.TryParse (parsedString, out parsedInt)) {
  518. return negative ? -parsedInt : parsedInt;
  519. }
  520. Error("Failed to read integer value: " + parsedString + ". Perhaps it's out of the range of acceptable numbers ink supports? (" + int.MinValue + " to " + int.MaxValue + ")");
  521. return null;
  522. }
  523. // No need to Begin/End rule since we never parse a newline, so keeping oldIndex is good enough
  524. public float? ParseFloat()
  525. {
  526. int oldIndex = index;
  527. int oldCharacterInLineIndex = characterInLineIndex;
  528. int? leadingInt = ParseInt ();
  529. if (leadingInt != null) {
  530. if (ParseString (".") != null) {
  531. var afterDecimalPointStr = ParseCharactersFromCharSet (numbersCharacterSet);
  532. return float.Parse (leadingInt+"." + afterDecimalPointStr, System.Globalization.CultureInfo.InvariantCulture);
  533. }
  534. }
  535. // Roll back and fail
  536. index = oldIndex;
  537. characterInLineIndex = oldCharacterInLineIndex;
  538. return null;
  539. }
  540. // You probably want "endOfLine", since it handles endOfFile too.
  541. protected string ParseNewline()
  542. {
  543. int ruleId = BeginRule();
  544. // Optional \r, definite \n to support Windows (\r\n) and Mac/Unix (\n)
  545. // 2nd May 2016: Always collapse \r\n to just \n
  546. ParseString ("\r");
  547. if( ParseString ("\n") == null ) {
  548. return (string) FailRule(ruleId);
  549. } else {
  550. return (string) SucceedRule(ruleId, "\n");
  551. }
  552. }
  553. private char[] _chars;
  554. }
  555. }