Flexible Bible Reference Parser
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
4.8 KiB

  1. #include "fbrp.h"
  2. // Struct to store read tokens
  3. struct Read {
  4. char text[MAX_WORD];
  5. int length;
  6. int type;
  7. };
  8. enum Types {
  9. DEFAULT,
  10. DIGIT,
  11. ALPHA,
  12. SEPERATOR,
  13. RANGE,
  14. MULTIPLE
  15. };
  16. // Test for a char in a string. Ex: 'c' in "cake"
  17. int testCharString(char test, char seperators[]) {
  18. for (int c = 0; seperators[c] != '\0'; c++) {
  19. if (seperators[c] == test) {
  20. return 1;
  21. }
  22. }
  23. return 0;
  24. }
  25. // Test type of a char
  26. int determineType(char input) {
  27. if ((input >= 'A' && input <= 'Z') || (input >= 'a' && input <= 'z')) {
  28. return ALPHA;
  29. } else if (input >= '0' && input <= '9') {
  30. return DIGIT;
  31. } else if (testCharString(input, "_ :./\n\0")) {
  32. return SEPERATOR;
  33. } else if (input == '-') {
  34. return RANGE;
  35. } else if (input == ',') {
  36. return MULTIPLE;
  37. } else {
  38. return SEPERATOR;
  39. }
  40. }
  41. // Custom STRTOL like function
  42. int strspt(char string[], char *result) {
  43. int integer = 0;
  44. for (int c = 0; string[c] != '\0'; c++) {
  45. int charType = determineType(string[c]);
  46. if (charType == DIGIT) {
  47. integer = integer * 10;
  48. integer += string[c] - '0';
  49. } else if (charType == ALPHA) {
  50. *result = string[c];
  51. result++;
  52. }
  53. }
  54. *result = '\0';
  55. return integer;
  56. }
  57. void mstrcat(char *s, char *t) {
  58. while(*s++);
  59. --s;
  60. while((*s++ = *t++));
  61. }
  62. // Set the range values for chapter or verses
  63. // A complicated use of pointers could have been used,
  64. // but this is overall simpler. (and safer?)
  65. void setInt(struct FbrpReference *ref, int on, int currentlyOn, int value, int append) {
  66. if (currentlyOn == 1) {
  67. ref->chapter[ref->chapterLength].range[on] = value;
  68. ref->chapterLength += append;
  69. } else if (currentlyOn == 2) {
  70. ref->verse[ref->verseLength].range[on] = value;
  71. ref->verseLength += append;
  72. }
  73. }
  74. // Main parsing function.
  75. void fbrp_parse(struct FbrpReference *ref, char string[]) {
  76. // 2D Array for interpreting
  77. struct Read read[MAX_READ];
  78. int readX = 0;
  79. int readY = 0;
  80. int lastType = 0;
  81. int partType = 0;
  82. read[0].length = 0;
  83. for (int c = 0; string[c] != '\0'; c++) {
  84. int type = determineType(string[c]);
  85. // Skip seperator, but set lastType
  86. if (type == SEPERATOR) {
  87. lastType = type;
  88. // Set type when on last char
  89. if (string[c + 1] == '\0') {
  90. read[readY].type = partType;
  91. }
  92. continue;
  93. }
  94. // Jump to next part in read
  95. if (type != lastType && c != 0) {
  96. read[readY].text[readX] = '\0';
  97. read[readY].type = partType;
  98. readY++;
  99. read[readY].length = 0;
  100. readX = 0;
  101. partType = 0;
  102. }
  103. partType = type;
  104. // Append the character, make sure it
  105. // doesn't go out of bounds
  106. if (readX <= MAX_WORD - 2) {
  107. read[readY].text[readX] = string[c];
  108. readX++;
  109. }
  110. read[readY].length++;
  111. lastType = type;
  112. }
  113. read[readY].text[readX] = '\0';
  114. read[readY].type = partType;
  115. // readY++ for the last part
  116. readY++;
  117. ref->chapterLength = 0;
  118. ref->verseLength = 0;
  119. ref->book[0] = '\0';
  120. // Now, start interpreting
  121. int jumping = 0;
  122. int currentlyOn = 0;
  123. for (int p = 0; p < readY; p++) {
  124. // Skip range/multiple chars
  125. if (read[p].type == RANGE || read[p].type == MULTIPLE) {
  126. continue;
  127. }
  128. // Try to parse what could be string, int, or both.
  129. char tryString[MAX_WORD];
  130. int tryInt = -1;
  131. tryInt = strspt(read[p].text, tryString);
  132. // If text encountered after chapter, then continue
  133. if (ref->chapterLength > 0 && tryString[0] != '\0') {
  134. continue;
  135. }
  136. // If chapter added and not jumping, then set verse
  137. if (ref->chapterLength >= 1 && jumping == 0) {
  138. currentlyOn = 2;
  139. }
  140. // if book and str undefined and p != 0 then set chapter
  141. if (currentlyOn == 0 && tryString[0] == '\0' && p != 0) {
  142. currentlyOn = 1;
  143. }
  144. // if book and str undefined and p == 0 then
  145. // assume part of book (Ex: [3] John)
  146. if (currentlyOn == 0 && tryString[0] == '\0' && p == 0) {
  147. mstrcat(ref->book, read[p].text);
  148. continue;
  149. }
  150. // if book and str valid then assume book
  151. if (currentlyOn == 0 && tryString[0] != '\0') {
  152. mstrcat(ref->book, read[p].text);
  153. continue;
  154. }
  155. // check next type
  156. int nextType = 0;
  157. if (p != readY - 1) {
  158. nextType = read[p + 1].type;
  159. }
  160. // Handle previous set jumps for range/multiple
  161. if (jumping == 1) {
  162. setInt(ref, 1, currentlyOn, tryInt, 1);
  163. jumping = 0;
  164. // Multiples after range
  165. if (nextType == MULTIPLE) {
  166. jumping = 2;
  167. continue;
  168. } else {
  169. currentlyOn = 2;
  170. continue;
  171. }
  172. } else if (jumping == 2) {
  173. jumping = 0;
  174. }
  175. // Check for the next type (range, multiple)
  176. if (nextType == RANGE) {
  177. setInt(ref, 0, currentlyOn, tryInt, 0);
  178. jumping = 1;
  179. continue;
  180. } else if (nextType == MULTIPLE) {
  181. setInt(ref, 0, currentlyOn, tryInt, 0);
  182. setInt(ref, 1, currentlyOn, tryInt, 1);
  183. jumping = 2;
  184. continue;
  185. }
  186. // Regular non range-multiple digit appending
  187. if (tryInt != -1 && jumping == 0) {
  188. setInt(ref, 0, currentlyOn, tryInt, 0);
  189. setInt(ref, 1, currentlyOn, tryInt, 1);
  190. }
  191. }
  192. }