taskjuggler 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. data/CHANGELOG +119 -0
  2. data/benchmarks/allocatedSlots.tjp +1602 -0
  3. data/benchmarks/booking.tjp +40 -30
  4. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/css/tjmanual.css +0 -0
  5. data/{test/TestSuite/Scheduler/Correct → benchmarks}/css/tjreport.css +1 -0
  6. data/benchmarks/gantt.tjp +57 -0
  7. data/benchmarks/htmltaskreport.tjp +26 -1
  8. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/details.png +0 -0
  9. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/flag-green.png +0 -0
  10. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/flag-red.png +0 -0
  11. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/flag-yellow.png +0 -0
  12. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/resource.png +0 -0
  13. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/resourcegroup.png +0 -0
  14. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/task.png +0 -0
  15. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/taskgroup.png +0 -0
  16. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/trend-down.png +0 -0
  17. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/trend-flat.png +0 -0
  18. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/trend-up.png +0 -0
  19. data/benchmarks/profile.clt +36082 -0
  20. data/benchmarks/profile.html +58182 -0
  21. data/benchmarks/runbench.rb +6 -0
  22. data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/scripts/wz_tooltip.js +0 -0
  23. data/doc/AppConfig.html +85 -37
  24. data/doc/Arguments.html +11 -1
  25. data/doc/CHANGELOG.html +131 -2
  26. data/doc/COPYING.html +11 -1
  27. data/doc/Object.html +12 -3
  28. data/doc/README.html +11 -1
  29. data/doc/RuntimeConfig.html +11 -1
  30. data/doc/String.html +11 -1
  31. data/doc/StringIO.html +11 -1
  32. data/doc/TaskJuggler.html +250 -219
  33. data/doc/TaskJuggler/Account.html +11 -1
  34. data/doc/TaskJuggler/AccountAttribute.html +11 -1
  35. data/doc/TaskJuggler/AccountScenario.html +11 -1
  36. data/doc/TaskJuggler/Allocation.html +11 -1
  37. data/doc/TaskJuggler/AllocationAttribute.html +11 -1
  38. data/doc/TaskJuggler/AttributeBase.html +11 -1
  39. data/doc/TaskJuggler/AttributeDefinition.html +11 -1
  40. data/doc/TaskJuggler/BatchProcessor.html +11 -1
  41. data/doc/TaskJuggler/Booking.html +11 -1
  42. data/doc/TaskJuggler/BookingListAttribute.html +11 -1
  43. data/doc/TaskJuggler/BooleanAttribute.html +11 -1
  44. data/doc/TaskJuggler/CSVFile.html +12 -2
  45. data/doc/TaskJuggler/CellSettingPattern.html +11 -1
  46. data/doc/TaskJuggler/CellSettingPatternList.html +11 -1
  47. data/doc/TaskJuggler/Charge.html +11 -1
  48. data/doc/TaskJuggler/ChargeListAttribute.html +11 -1
  49. data/doc/TaskJuggler/ChargeSet.html +11 -1
  50. data/doc/TaskJuggler/ChargeSetListAttribute.html +11 -1
  51. data/doc/TaskJuggler/CollisionDetector.html +1063 -0
  52. data/doc/TaskJuggler/ColumnListAttribute.html +11 -1
  53. data/doc/TaskJuggler/ColumnTable.html +11 -1
  54. data/doc/TaskJuggler/Daemon.html +11 -1
  55. data/doc/TaskJuggler/{OnShiftCache.html → DataCache.html} +127 -139
  56. data/doc/TaskJuggler/DataCacheEntry.html +711 -0
  57. data/doc/TaskJuggler/DateAttribute.html +11 -1
  58. data/doc/TaskJuggler/DefinitionListAttribute.html +11 -1
  59. data/doc/TaskJuggler/DependencyListAttribute.html +11 -1
  60. data/doc/TaskJuggler/DurationAttribute.html +16 -5
  61. data/doc/TaskJuggler/FileList.html +11 -1
  62. data/doc/TaskJuggler/FileRecord.html +11 -1
  63. data/doc/TaskJuggler/FixnumAttribute.html +19 -9
  64. data/doc/TaskJuggler/FlagListAttribute.html +29 -19
  65. data/doc/TaskJuggler/FloatAttribute.html +23 -13
  66. data/doc/TaskJuggler/FormatListAttribute.html +19 -9
  67. data/doc/TaskJuggler/GanttChart.html +94 -133
  68. data/doc/TaskJuggler/GanttContainer.html +11 -1
  69. data/doc/TaskJuggler/GanttHeader.html +11 -1
  70. data/doc/TaskJuggler/GanttHeaderScaleItem.html +11 -1
  71. data/doc/TaskJuggler/GanttLine.html +11 -1
  72. data/doc/TaskJuggler/GanttLoadStack.html +11 -1
  73. data/doc/TaskJuggler/GanttMilestone.html +11 -1
  74. data/doc/TaskJuggler/GanttRouter.html +247 -596
  75. data/doc/TaskJuggler/GanttTaskBar.html +11 -1
  76. data/doc/TaskJuggler/HTMLDocument.html +11 -1
  77. data/doc/TaskJuggler/HTMLGraphics.html +11 -1
  78. data/doc/TaskJuggler/Interval.html +11 -1
  79. data/doc/TaskJuggler/IntervalListAttribute.html +33 -23
  80. data/doc/TaskJuggler/JobInfo.html +11 -1
  81. data/doc/TaskJuggler/Journal.html +11 -1
  82. data/doc/TaskJuggler/JournalEntry.html +11 -1
  83. data/doc/TaskJuggler/JournalEntryList.html +11 -1
  84. data/doc/TaskJuggler/KeywordArray.html +11 -1
  85. data/doc/TaskJuggler/KeywordDocumentation.html +16 -6
  86. data/doc/TaskJuggler/Limits.html +11 -1
  87. data/doc/TaskJuggler/Limits/Limit.html +11 -1
  88. data/doc/TaskJuggler/LimitsAttribute.html +24 -14
  89. data/doc/TaskJuggler/ListAttributeBase.html +11 -1
  90. data/doc/TaskJuggler/Log.html +11 -1
  91. data/doc/TaskJuggler/LogFile.html +11 -1
  92. data/doc/TaskJuggler/LogicalAttribute.html +11 -1
  93. data/doc/TaskJuggler/LogicalExpression.html +11 -1
  94. data/doc/TaskJuggler/LogicalExpressionAttribute.html +19 -9
  95. data/doc/TaskJuggler/LogicalFlag.html +11 -1
  96. data/doc/TaskJuggler/LogicalFunction.html +11 -1
  97. data/doc/TaskJuggler/LogicalOperation.html +11 -1
  98. data/doc/TaskJuggler/Macro.html +11 -1
  99. data/doc/TaskJuggler/MacroTable.html +11 -1
  100. data/doc/TaskJuggler/ManagerResponsibilities.html +11 -1
  101. data/doc/TaskJuggler/ManagerStatusRecord.html +11 -1
  102. data/doc/TaskJuggler/Message.html +11 -1
  103. data/doc/TaskJuggler/MessageHandler.html +11 -1
  104. data/doc/TaskJuggler/Navigator.html +12 -2
  105. data/doc/TaskJuggler/NavigatorElement.html +11 -1
  106. data/doc/TaskJuggler/NikuProject.html +11 -1
  107. data/doc/TaskJuggler/NikuReport.html +11 -1
  108. data/doc/TaskJuggler/NikuResource.html +11 -1
  109. data/doc/TaskJuggler/NodeListAttribute.html +17 -7
  110. data/doc/TaskJuggler/PlaceHolderCell.html +722 -0
  111. data/doc/TaskJuggler/ProcessIntercom.html +11 -1
  112. data/doc/TaskJuggler/ProcessIntercomIface.html +11 -1
  113. data/doc/TaskJuggler/Project.html +587 -500
  114. data/doc/TaskJuggler/ProjectBroker.html +11 -1
  115. data/doc/TaskJuggler/ProjectBrokerIface.html +11 -1
  116. data/doc/TaskJuggler/ProjectFileParser.html +205 -192
  117. data/doc/TaskJuggler/ProjectFileScanner.html +230 -207
  118. data/doc/TaskJuggler/ProjectRecord.html +11 -1
  119. data/doc/TaskJuggler/ProjectServer.html +11 -1
  120. data/doc/TaskJuggler/ProjectServerIface.html +11 -1
  121. data/doc/TaskJuggler/PropertyAttribute.html +19 -9
  122. data/doc/TaskJuggler/PropertyList.html +95 -83
  123. data/doc/TaskJuggler/PropertySet.html +11 -1
  124. data/doc/TaskJuggler/PropertyTreeNode.html +11 -1
  125. data/doc/TaskJuggler/Query.html +234 -232
  126. data/doc/TaskJuggler/RTFHandlers.html +11 -1
  127. data/doc/TaskJuggler/RTFNavigator.html +11 -1
  128. data/doc/TaskJuggler/RTFQuery.html +11 -1
  129. data/doc/TaskJuggler/RTFReport.html +11 -1
  130. data/doc/TaskJuggler/RTFReportLink.html +11 -1
  131. data/doc/TaskJuggler/RTFWithQuerySupport.html +11 -1
  132. data/doc/TaskJuggler/RealFormat.html +12 -2
  133. data/doc/TaskJuggler/RealFormatAttribute.html +15 -5
  134. data/doc/TaskJuggler/ReferenceAttribute.html +38 -28
  135. data/doc/TaskJuggler/Report.html +96 -113
  136. data/doc/TaskJuggler/ReportBase.html +161 -152
  137. data/doc/TaskJuggler/ReportContext.html +11 -1
  138. data/doc/TaskJuggler/ReportServer.html +59 -48
  139. data/doc/TaskJuggler/ReportServerIface.html +51 -41
  140. data/doc/TaskJuggler/ReportServerRecord.html +11 -1
  141. data/doc/TaskJuggler/ReportServlet.html +11 -1
  142. data/doc/TaskJuggler/ReportTable.html +46 -25
  143. data/doc/TaskJuggler/ReportTableCell.html +296 -275
  144. data/doc/TaskJuggler/ReportTableColumn.html +14 -4
  145. data/doc/TaskJuggler/ReportTableLegend.html +11 -1
  146. data/doc/TaskJuggler/ReportTableLine.html +19 -7
  147. data/doc/TaskJuggler/Resource.html +12 -2
  148. data/doc/TaskJuggler/ResourceListAttribute.html +40 -30
  149. data/doc/TaskJuggler/ResourceListRE.html +11 -1
  150. data/doc/TaskJuggler/ResourceScenario.html +708 -565
  151. data/doc/TaskJuggler/RichText.html +54 -36
  152. data/doc/TaskJuggler/RichTextAttribute.html +31 -21
  153. data/doc/TaskJuggler/RichTextDocument.html +11 -1
  154. data/doc/TaskJuggler/RichTextElement.html +11 -1
  155. data/doc/TaskJuggler/RichTextFunctionExample.html +11 -1
  156. data/doc/TaskJuggler/RichTextFunctionHandler.html +11 -1
  157. data/doc/TaskJuggler/RichTextImage.html +11 -1
  158. data/doc/TaskJuggler/RichTextIntermediate.html +81 -71
  159. data/doc/TaskJuggler/RichTextParser.html +88 -33
  160. data/doc/TaskJuggler/RichTextScanner.html +45 -35
  161. data/doc/TaskJuggler/RichTextSnip.html +11 -1
  162. data/doc/TaskJuggler/RichTextSyntaxRules.html +436 -389
  163. data/doc/TaskJuggler/Scenario.html +11 -1
  164. data/doc/TaskJuggler/ScenarioData.html +11 -1
  165. data/doc/TaskJuggler/ScenarioListAttribute.html +23 -13
  166. data/doc/TaskJuggler/Scoreboard.html +92 -73
  167. data/doc/TaskJuggler/SheetHandlerBase.html +11 -1
  168. data/doc/TaskJuggler/SheetReceiver.html +11 -1
  169. data/doc/TaskJuggler/SheetSender.html +11 -1
  170. data/doc/TaskJuggler/Shift.html +11 -1
  171. data/doc/TaskJuggler/ShiftAssignment.html +11 -1
  172. data/doc/TaskJuggler/ShiftAssignments.html +11 -1
  173. data/doc/TaskJuggler/ShiftAssignmentsAttribute.html +24 -14
  174. data/doc/TaskJuggler/ShiftScenario.html +11 -1
  175. data/doc/TaskJuggler/SimpleQueryExpander.html +11 -1
  176. data/doc/TaskJuggler/SortListAttribute.html +21 -11
  177. data/doc/TaskJuggler/SourceFileInfo.html +11 -1
  178. data/doc/TaskJuggler/StatusSheetReceiver.html +11 -1
  179. data/doc/TaskJuggler/StatusSheetReport.html +11 -1
  180. data/doc/TaskJuggler/StatusSheetSender.html +112 -11
  181. data/doc/TaskJuggler/StringAttribute.html +23 -13
  182. data/doc/TaskJuggler/SymbolAttribute.html +19 -9
  183. data/doc/TaskJuggler/SyntaxReference.html +80 -71
  184. data/doc/TaskJuggler/TOCEntry.html +11 -1
  185. data/doc/TaskJuggler/TSResourceRecord.html +11 -1
  186. data/doc/TaskJuggler/TSTaskRecord.html +11 -1
  187. data/doc/TaskJuggler/TableColumnDefinition.html +11 -1
  188. data/doc/TaskJuggler/TableOfContents.html +11 -1
  189. data/doc/TaskJuggler/TableReport.html +422 -411
  190. data/doc/TaskJuggler/Task.html +11 -1
  191. data/doc/TaskJuggler/TaskDependency.html +11 -1
  192. data/doc/TaskJuggler/TaskListAttribute.html +33 -23
  193. data/doc/TaskJuggler/TaskListRE.html +11 -1
  194. data/doc/TaskJuggler/TaskScenario.html +2007 -1919
  195. data/doc/TaskJuggler/TextFormatter.html +11 -1
  196. data/doc/TaskJuggler/TextParser.html +421 -612
  197. data/doc/TaskJuggler/TextParser/Pattern.html +410 -211
  198. data/doc/TaskJuggler/TextParser/Rule.html +224 -152
  199. data/doc/TaskJuggler/TextParser/StackElement.html +190 -28
  200. data/doc/TaskJuggler/TextParser/State.html +989 -0
  201. data/doc/TaskJuggler/TextParser/StateTransition.html +782 -0
  202. data/doc/TaskJuggler/TextParser/TextParserResultArray.html +25 -14
  203. data/doc/TaskJuggler/TextParser/TokenDoc.html +11 -1
  204. data/doc/TaskJuggler/TextReport.html +11 -1
  205. data/doc/TaskJuggler/TextScanner.html +285 -273
  206. data/doc/TaskJuggler/TextScanner/BufferStreamHandle.html +17 -7
  207. data/doc/TaskJuggler/TextScanner/FileStreamHandle.html +24 -14
  208. data/doc/TaskJuggler/TextScanner/MacroStackEntry.html +11 -1
  209. data/doc/TaskJuggler/TextScanner/StreamHandle.html +64 -52
  210. data/doc/TaskJuggler/TimeSheet.html +11 -1
  211. data/doc/TaskJuggler/TimeSheetReceiver.html +11 -1
  212. data/doc/TaskJuggler/TimeSheetRecord.html +11 -1
  213. data/doc/TaskJuggler/TimeSheetReport.html +11 -1
  214. data/doc/TaskJuggler/TimeSheetSender.html +11 -1
  215. data/doc/TaskJuggler/TimeSheetSummary.html +11 -1
  216. data/doc/TaskJuggler/TimeSheets.html +11 -1
  217. data/doc/TaskJuggler/Tj3AppBase.html +11 -1
  218. data/doc/TaskJuggler/Tj3Client.html +11 -1
  219. data/doc/TaskJuggler/Tj3Daemon.html +11 -1
  220. data/doc/TaskJuggler/Tj3SheetAppBase.html +11 -1
  221. data/doc/TaskJuggler/Tj3SsReceiver.html +11 -1
  222. data/doc/TaskJuggler/Tj3SsSender.html +11 -1
  223. data/doc/TaskJuggler/Tj3TsReceiver.html +11 -1
  224. data/doc/TaskJuggler/Tj3TsSender.html +11 -1
  225. data/doc/TaskJuggler/Tj3TsSummary.html +11 -1
  226. data/doc/TaskJuggler/TjException.html +11 -1
  227. data/doc/TaskJuggler/TjTime.html +474 -324
  228. data/doc/TaskJuggler/TjpExample.html +11 -1
  229. data/doc/TaskJuggler/TjpExportRE.html +11 -1
  230. data/doc/TaskJuggler/TjpSyntaxRules.html +3731 -3662
  231. data/doc/TaskJuggler/URLParameter.html +11 -1
  232. data/doc/TaskJuggler/UserManual.html +11 -1
  233. data/doc/TaskJuggler/VimSyntax.html +11 -1
  234. data/doc/TaskJuggler/WebServer.html +11 -1
  235. data/doc/TaskJuggler/WorkingHours.html +295 -221
  236. data/doc/TaskJuggler/WorkingHoursAttribute.html +11 -1
  237. data/doc/TaskJuggler/XMLBlob.html +11 -1
  238. data/doc/TaskJuggler/XMLComment.html +11 -1
  239. data/doc/TaskJuggler/XMLDocument.html +11 -1
  240. data/doc/TaskJuggler/XMLElement.html +11 -1
  241. data/doc/TaskJuggler/XMLNamedText.html +11 -1
  242. data/doc/TaskJuggler/XMLText.html +11 -1
  243. data/doc/index.html +694 -624
  244. data/doc/lib/AppConfig_rb.html +1 -1
  245. data/doc/lib/Attributes_rb.html +1 -1
  246. data/doc/lib/Booking_rb.html +1 -1
  247. data/doc/lib/DataCache_rb.html +69 -0
  248. data/doc/lib/KeywordDocumentation_rb.html +1 -1
  249. data/doc/lib/ProjectFileParser_rb.html +1 -1
  250. data/doc/lib/ProjectFileScanner_rb.html +1 -1
  251. data/doc/lib/Project_rb.html +1 -1
  252. data/doc/lib/PropertyList_rb.html +1 -1
  253. data/doc/lib/Query_rb.html +1 -1
  254. data/doc/lib/RealFormat_rb.html +1 -1
  255. data/doc/lib/ResourceScenario_rb.html +1 -1
  256. data/doc/lib/Resource_rb.html +1 -1
  257. data/doc/lib/RichTextParser_rb.html +1 -1
  258. data/doc/lib/RichTextScanner_rb.html +1 -1
  259. data/doc/lib/RichTextSyntaxRules_rb.html +1 -1
  260. data/doc/lib/RichText_rb.html +1 -1
  261. data/doc/lib/Scoreboard_rb.html +1 -1
  262. data/doc/lib/StatusSheetSender_rb.html +1 -1
  263. data/doc/lib/SyntaxReference_rb.html +1 -1
  264. data/doc/lib/TaskJuggler_rb.html +1 -1
  265. data/doc/lib/TaskScenario_rb.html +3 -1
  266. data/doc/lib/TextParser/Pattern_rb.html +3 -1
  267. data/doc/lib/TextParser/Rule_rb.html +3 -1
  268. data/doc/lib/TextParser/StackElement_rb.html +3 -1
  269. data/doc/lib/TextParser/State_rb.html +65 -0
  270. data/doc/lib/TextParser_rb.html +1 -1
  271. data/doc/lib/TextScanner_rb.html +1 -1
  272. data/doc/lib/Tj3Config_rb.html +1 -1
  273. data/doc/lib/TjTime_rb.html +1 -1
  274. data/doc/lib/TjpSyntaxRules_rb.html +1 -1
  275. data/doc/lib/WorkingHours_rb.html +3 -1
  276. data/doc/lib/daemon/ReportServer_rb.html +1 -1
  277. data/doc/lib/reports/CSVFile_rb.html +1 -1
  278. data/doc/lib/reports/CollisionDetector_rb.html +67 -0
  279. data/doc/lib/reports/GanttChart_rb.html +1 -1
  280. data/doc/lib/reports/GanttRouter_rb.html +3 -1
  281. data/doc/lib/reports/Navigator_rb.html +1 -1
  282. data/doc/lib/reports/ReportBase_rb.html +1 -1
  283. data/doc/lib/reports/ReportTableCell_rb.html +1 -1
  284. data/doc/lib/reports/ReportTableColumn_rb.html +1 -1
  285. data/doc/lib/reports/ReportTableLine_rb.html +1 -1
  286. data/doc/lib/reports/ReportTable_rb.html +1 -1
  287. data/doc/lib/reports/Report_rb.html +1 -1
  288. data/doc/lib/reports/TableReport_rb.html +1 -1
  289. data/doc/lib/taskjuggler3_rb.html +1 -1
  290. data/examples/tutorial.tjp +1 -2
  291. data/lib/AppConfig.rb +10 -4
  292. data/lib/Attributes.rb +4 -4
  293. data/lib/DataCache.rb +124 -0
  294. data/lib/KeywordDocumentation.rb +5 -5
  295. data/lib/Project.rb +54 -10
  296. data/lib/ProjectFileParser.rb +10 -9
  297. data/lib/ProjectFileScanner.rb +38 -25
  298. data/lib/PropertyList.rb +6 -4
  299. data/lib/Query.rb +0 -8
  300. data/lib/RealFormat.rb +1 -1
  301. data/lib/Resource.rb +1 -1
  302. data/lib/ResourceScenario.rb +96 -31
  303. data/lib/RichText.rb +17 -5
  304. data/lib/RichTextParser.rb +22 -9
  305. data/lib/RichTextScanner.rb +34 -34
  306. data/lib/RichTextSyntaxRules.rb +41 -36
  307. data/lib/Scoreboard.rb +16 -7
  308. data/lib/StatusSheetSender.rb +63 -0
  309. data/lib/SyntaxReference.rb +9 -10
  310. data/lib/TaskJuggler.rb +28 -4
  311. data/lib/TaskScenario.rb +66 -19
  312. data/lib/TextParser.rb +219 -384
  313. data/lib/TextParser/Pattern.rb +168 -49
  314. data/lib/TextParser/Rule.rb +33 -17
  315. data/lib/TextParser/StackElement.rb +42 -2
  316. data/lib/TextParser/State.rb +175 -0
  317. data/lib/TextScanner.rb +19 -15
  318. data/lib/Tj3Config.rb +1 -1
  319. data/lib/TjTime.rb +111 -3
  320. data/lib/TjpSyntaxRules.rb +122 -66
  321. data/lib/WorkingHours.rb +91 -186
  322. data/lib/daemon/ReportServer.rb +3 -2
  323. data/lib/reports/CSVFile.rb +1 -1
  324. data/lib/reports/CollisionDetector.rb +177 -0
  325. data/lib/reports/GanttChart.rb +25 -41
  326. data/lib/reports/GanttRouter.rb +104 -233
  327. data/lib/reports/Navigator.rb +1 -1
  328. data/lib/reports/Report.rb +9 -33
  329. data/lib/reports/ReportBase.rb +0 -1
  330. data/lib/reports/ReportTable.rb +19 -8
  331. data/lib/reports/ReportTableCell.rb +61 -25
  332. data/lib/reports/ReportTableColumn.rb +2 -2
  333. data/lib/reports/ReportTableLine.rb +4 -2
  334. data/lib/reports/TableReport.rb +1 -0
  335. data/lib/taskjuggler3.rb +0 -1
  336. data/manual/Installation +7 -3
  337. data/manual/Intro +12 -10
  338. data/manual/The_TaskJuggler_Syntax +4 -4
  339. data/test/TestSuite/CSV-Reports/celltext-Reference.csv +14 -14
  340. data/test/TestSuite/CSV-Reports/genrefs +1 -1
  341. data/test/TestSuite/CSV-Reports/resourcereport-Reference.csv +4 -4
  342. data/test/TestSuite/CSV-Reports/resourcereport_with_tasks-Reference.csv +22 -22
  343. data/test/TestSuite/CSV-Reports/sortByTree-Reference.csv +14 -14
  344. data/test/TestSuite/CSV-Reports/sortBy_duration.down-Reference.csv +14 -14
  345. data/test/TestSuite/CSV-Reports/sortBy_effort.up-Reference.csv +14 -14
  346. data/test/TestSuite/CSV-Reports/sortBy_plan.start.down-Reference.csv +14 -14
  347. data/test/TestSuite/CSV-Reports/taskreport-Reference.csv +14 -14
  348. data/test/TestSuite/CSV-Reports/taskreport_with_resources-Reference.csv +32 -24
  349. data/test/TestSuite/CSV-Reports/weekly-Reference.csv +13 -0
  350. data/test/TestSuite/CSV-Reports/weekly.tjp +9 -0
  351. data/test/TestSuite/HTML-Reports/css/tjreport.css +7 -2
  352. data/test/TestSuite/HTML-Reports/depArrows.html +839 -830
  353. data/test/TestSuite/HTML-Reports/depArrows.tjp +12 -12
  354. data/test/TestSuite/HTML-Reports/profile.html +37581 -0
  355. data/test/TestSuite/ReportGenerator/Errors/no_report_defined.tjp +7 -0
  356. data/test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.tjp +1 -1
  357. data/test/TestSuite/StatusSheets/TimeSheets/2002-03-01/missing-reports +2 -0
  358. data/test/TestSuite/StatusSheets/run +2 -0
  359. data/test/TestSuite/Syntax/Correct/Booking.tjp +13 -5
  360. data/test/TestSuite/Syntax/Correct/ResourceRoot.tjp +21 -0
  361. data/test/TestSuite/Syntax/Correct/RollupResource.tjp +21 -0
  362. data/test/TestSuite/Syntax/Correct/TaskRoot.tjp +1 -1
  363. data/test/TestSuite/Syntax/Errors/empty.tjp +1 -1
  364. data/test/TestSuite/Syntax/Errors/include_before_project.tjp +2 -0
  365. data/test/TestSuite/Syntax/Errors/no_reduce.tjp +6 -0
  366. data/test/TestSuite/Syntax/Errors/unsupported_token.tjp +1 -1
  367. data/test/TestSuite/TimeSheets/run +1 -1
  368. data/test/test_CSV-Reports.rb +2 -4
  369. data/test/test_CollisionDetector.rb +85 -0
  370. data/test/test_Project.rb +2 -2
  371. data/test/test_ProjectFileScanner.rb +73 -31
  372. data/test/test_Query.rb +2 -2
  373. data/test/test_ReportGenerator.rb +1 -1
  374. data/test/test_RichText.rb +4 -4
  375. data/test/test_WorkingHours.rb +150 -11
  376. metadata +75 -67
  377. data/test/TestSuite/ReportGenerator/Errors/css/tjreport.css +0 -407
  378. data/test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.html +0 -26
  379. data/test/TestSuite/Scheduler/Correct/Allocate.html +0 -3210
  380. data/test/TestSuite/Scheduler/Correct/Container.html +0 -349
  381. data/test/TestSuite/Scheduler/Correct/Limits.html +0 -4964
  382. data/test/TestSuite/Scheduler/Correct/Shift.html +0 -1719
  383. data/test/TestSuite/Scheduler/Correct/Shift2.html +0 -476
  384. data/test/TestSuite/Scheduler/Correct/css/tjmanual.css +0 -66
  385. data/test/TestSuite/Scheduler/Correct/icons/details.png +0 -0
  386. data/test/TestSuite/Scheduler/Correct/icons/flag-green.png +0 -0
  387. data/test/TestSuite/Scheduler/Correct/icons/flag-red.png +0 -0
  388. data/test/TestSuite/Scheduler/Correct/icons/flag-yellow.png +0 -0
  389. data/test/TestSuite/Scheduler/Correct/icons/resource.png +0 -0
  390. data/test/TestSuite/Scheduler/Correct/icons/resourcegroup.png +0 -0
  391. data/test/TestSuite/Scheduler/Correct/icons/task.png +0 -0
  392. data/test/TestSuite/Scheduler/Correct/icons/taskgroup.png +0 -0
  393. data/test/TestSuite/Scheduler/Correct/icons/trend-down.png +0 -0
  394. data/test/TestSuite/Scheduler/Correct/icons/trend-flat.png +0 -0
  395. data/test/TestSuite/Scheduler/Correct/icons/trend-up.png +0 -0
  396. data/test/TestSuite/Scheduler/Correct/scripts/wz_tooltip.js +0 -1301
  397. data/test/TestSuite/Scheduler/Errors/css/tjmanual.css +0 -66
  398. data/test/TestSuite/Scheduler/Errors/css/tjreport.css +0 -407
  399. data/test/TestSuite/Scheduler/Errors/icons/details.png +0 -0
  400. data/test/TestSuite/Scheduler/Errors/icons/flag-green.png +0 -0
  401. data/test/TestSuite/Scheduler/Errors/icons/flag-red.png +0 -0
  402. data/test/TestSuite/Scheduler/Errors/icons/flag-yellow.png +0 -0
  403. data/test/TestSuite/Scheduler/Errors/icons/resource.png +0 -0
  404. data/test/TestSuite/Scheduler/Errors/icons/resourcegroup.png +0 -0
  405. data/test/TestSuite/Scheduler/Errors/icons/task.png +0 -0
  406. data/test/TestSuite/Scheduler/Errors/icons/taskgroup.png +0 -0
  407. data/test/TestSuite/Scheduler/Errors/icons/trend-down.png +0 -0
  408. data/test/TestSuite/Scheduler/Errors/icons/trend-flat.png +0 -0
  409. data/test/TestSuite/Scheduler/Errors/icons/trend-up.png +0 -0
  410. data/test/TestSuite/Scheduler/Errors/scripts/wz_tooltip.js +0 -1301
  411. data/test/TestSuite/StatusSheets/resrep.tji +0 -7
  412. data/test/TestSuite/StatusSheets/tj3d.log +0 -312
  413. data/test/TestSuite/Syntax/Correct/Managers.html +0 -263
  414. data/test/TestSuite/TimeSheets/acceptable_intervals +0 -1
  415. data/test/TestSuite/TimeSheets/statussheets.log +0 -1
@@ -196,17 +196,16 @@ class TaskJuggler
196
196
  # Push pattern onto 'stack'.
197
197
  stack[pattern] = true
198
198
 
199
- if pattern[0] == '_{' && pattern[2] == '_}'
199
+ if pattern[0][1] == '{' && pattern[2][1] == '}'
200
200
  # We have found an optional attribute pattern!
201
201
  return attributes(pattern[1], false)
202
202
  end
203
203
 
204
204
  # If a token of the pattern is a reference, we recursively
205
205
  # follow the reference to the next pattern.
206
- pattern.each do |token|
207
- if token[0] == ?!
208
- token = token.slice(1, token.length - 1)
209
- rule = @parser.rules[token]
206
+ pattern.each do |type, name|
207
+ if type == :reference
208
+ rule = @parser.rules[name]
210
209
  # Rules with multiple patterns won't lead to attributes.
211
210
  next if rule.patterns.length > 1
212
211
 
@@ -222,23 +221,23 @@ class TaskJuggler
222
221
  # rule. The patterns are returned as a hash. For each pattern the hashed
223
222
  # boolean value specifies whether the attribute is scenario specific or not.
224
223
  def attributes(token, scenarioSpecific)
225
- raise "Token #{token} must reference a rule" if token[0] != ?!
226
- token = token.slice(1, token.length - 1)
224
+ raise "Token #{token} must reference a rule" if token[0] != :reference
225
+ token = token[1]
227
226
  # Find the matching rule.
228
227
  rule = @parser.rules[token]
229
228
  attrs = {}
230
229
  # Now we look at the first token of each pattern.
231
230
  rule.patterns.each do |pattern|
232
- if pattern[0][0] == ?_
231
+ if pattern[0][0] == :literal
233
232
  # If it's a terminal symbol, we found what we are looking for. We add
234
233
  # it to the attrs hash and mark it as non scenario specific.
235
234
  attrs[pattern] = scenarioSpecific
236
- elsif pattern[0] == '!scenarioIdCol'
235
+ elsif pattern[0][0] == :reference && pattern[0][1] == :scenarioIdCol
237
236
  # A reference to the !scenarioId rule marks the next token of the
238
237
  # pattern as a reference to a rule with all scenario specific
239
238
  # attributes.
240
239
  attrs.merge!(attributes(pattern[1], true))
241
- elsif pattern[0][0] == ?!
240
+ elsif pattern[0][0] == :reference
242
241
  # In case we have a reference to another rule, we just follow the
243
242
  # reference. If the pattern is documented we don't have to follow the
244
243
  # reference. We can use the pattern instead.
data/lib/TaskJuggler.rb CHANGED
@@ -14,6 +14,8 @@ require 'drb'
14
14
  require 'Project'
15
15
  require 'MessageHandler'
16
16
  require 'Log'
17
+ # Only needed during profiling.
18
+ #require 'ruby-prof'
17
19
 
18
20
  # The TaskJuggler class models the object that provides access to the
19
21
  # fundamental features of the TaskJuggler software. It can read project
@@ -41,6 +43,7 @@ class TaskJuggler
41
43
  master = true
42
44
  @project = nil
43
45
 
46
+ #RubyProf.start
44
47
  @parser = ProjectFileParser.new(@messageHandler)
45
48
  files.each do |file|
46
49
  begin
@@ -54,7 +57,7 @@ class TaskJuggler
54
57
  end
55
58
  if master
56
59
  # The first file is considered the master file.
57
- if (@project = @parser.parse('project')) == false
60
+ if (@project = @parser.parse(:project)) == false
58
61
  Log.exit('parser')
59
62
  return false
60
63
  end
@@ -62,7 +65,7 @@ class TaskJuggler
62
65
  else
63
66
  # All other files.
64
67
  @parser.setGlobalMacros
65
- if @parser.parse('properties') == false
68
+ if @parser.parse(:propertiesFile) == false
66
69
  Log.exit('parser')
67
70
  return false
68
71
  end
@@ -71,6 +74,16 @@ class TaskJuggler
71
74
  @parser.close
72
75
  end
73
76
 
77
+ #profile = RubyProf.stop
78
+ #printer = RubyProf::GraphHtmlPrinter.new(profile)
79
+ #File.open("profile.html", "w") do |file|
80
+ # printer.print(file)
81
+ #end
82
+ #printer = RubyProf::CallTreePrinter.new(profile)
83
+ #File.open("profile.clt", "w") do |file|
84
+ # printer.print(file)
85
+ #end
86
+
74
87
  # For the report server mode we may need to keep the parser. Otherwise,
75
88
  # destroy it.
76
89
  @parser = nil unless keepParser
@@ -123,12 +136,23 @@ class TaskJuggler
123
136
  # this method can be called. It returns true if no error occured, false
124
137
  # otherwise.
125
138
  def generateReports(outputDir = './')
139
+ @project.checkReports
126
140
  outputDir += '/' unless outputDir.empty? || outputDir[-1] == '/'
127
141
  @project.outputDir = outputDir
128
142
  Log.enter('reports', 'Generating reports ...')
129
143
 
130
144
  begin
145
+ #RubyProf.start
131
146
  @project.generateReports(@maxCpuCores)
147
+ #profile = RubyProf.stop
148
+ #printer = RubyProf::GraphHtmlPrinter.new(profile)
149
+ #File.open("profile.html", "w") do |file|
150
+ # printer.print(file)
151
+ #end
152
+ #printer = RubyProf::CallTreePrinter.new(profile)
153
+ #File.open("profile.clt", "w") do |file|
154
+ # printer.print(file)
155
+ #end
132
156
  rescue TjException => msg
133
157
  if msg.message && !msg.message.empty?
134
158
  @messageHandler.critical('generate_reports', msg.message)
@@ -186,7 +210,7 @@ class TaskJuggler
186
210
  # Make sure we don't use data from old time sheets or Journal entries.
187
211
  @project.timeSheets.clear
188
212
  @project['journal'] = Journal.new
189
- return false unless (ts = parseFile(fileName, 'timeSheetFile'))
213
+ return false unless (ts = parseFile(fileName, :timeSheetFile))
190
214
  return false unless @project.checkTimeSheets
191
215
  queryAttrs = { 'project' => @project,
192
216
  'property' => ts.resource,
@@ -215,7 +239,7 @@ class TaskJuggler
215
239
  def checkStatusSheet(fileName)
216
240
  begin
217
241
  Log.enter('checkStatusSheet', 'Parsing #{fileName} ...')
218
- return false unless (ss = parseFile(fileName, 'statusSheetFile'))
242
+ return false unless (ss = parseFile(fileName, :statusSheetFile))
219
243
  queryAttrs = { 'project' => @project,
220
244
  'property' => ss[0],
221
245
  'scopeProperty' => nil,
data/lib/TaskScenario.rb CHANGED
@@ -11,6 +11,7 @@
11
11
  #
12
12
 
13
13
  require 'ScenarioData'
14
+ require 'DataCache'
14
15
 
15
16
  class TaskJuggler
16
17
 
@@ -24,6 +25,7 @@ class TaskJuggler
24
25
 
25
26
  # A list of all allocated leaf resources.
26
27
  @candidates = []
28
+ @dCache = DataCache.instance
27
29
  end
28
30
 
29
31
  # Call this function to reset all scheduling related data prior to
@@ -324,9 +326,19 @@ class TaskJuggler
324
326
  # be called to do some more housekeeping. It computes some derived data
325
327
  # based on the just scheduled values.
326
328
  def finishScheduling
327
- # The completion degree calculation is done recursively. So we only call
328
- # it for top-level tasks.
329
- calcCompletion unless @property.parent
329
+ # Recursively descend into all child tasks.
330
+ @property.children.each do |task|
331
+ task.finishScheduling(@scenarioIdx)
332
+ end
333
+
334
+ if (parent = @property.parent)
335
+ # Add the assigned resources to the parent task list.
336
+ a('assignedresources').each do |resource|
337
+ unless parent['assignedresources', @scenarioIdx].include?(resource)
338
+ parent['assignedresources', @scenarioIdx] << resource
339
+ end
340
+ end
341
+ end
330
342
 
331
343
  # This list is no longer needed, so let's save some memory. Set it to
332
344
  # nil so we can detect accidental use.
@@ -450,7 +462,8 @@ class TaskJuggler
450
462
  "#{dependency.gapDuration / (60 * 60 * 24)} days after " +
451
463
  "#{dependency.onEnd ? 'end' : 'start'} of task " +
452
464
  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
453
- "because the task is scheduled ALAP (finish-to-start).")
465
+ "because the task is scheduled ALAP (finish-to-start) or " +
466
+ "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
454
467
  end
455
468
  end
456
469
  if dependency.gapLength > 0
@@ -461,7 +474,8 @@ class TaskJuggler
461
474
  "working days after " +
462
475
  "#{dependency.onEnd ? 'end' : 'start'} of task " +
463
476
  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
464
- "because the task is scheduled ALAP (finish-to-start).")
477
+ "because the task is scheduled ALAP (finish-to-start) or " +
478
+ "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
465
479
  end
466
480
  end
467
481
  end
@@ -483,7 +497,8 @@ class TaskJuggler
483
497
  "#{dependency.gapDuration / (60 * 60 * 24)} days before " +
484
498
  "#{dependency.onEnd ? 'end' : 'start'} of task " +
485
499
  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
486
- "because the task is scheduled ASAP (start-to-finish).")
500
+ "because the task is scheduled ASAP (start-to-finish) or " +
501
+ "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
487
502
  end
488
503
  end
489
504
  if dependency.gapLength > 0
@@ -494,7 +509,8 @@ class TaskJuggler
494
509
  "working days before " +
495
510
  "#{dependency.onEnd ? 'end' : 'start'} of task " +
496
511
  "#{task.fullId}. TaskJuggler cannot enforce this condition " +
497
- "because the task is scheduled ASAP (start-to-finish).")
512
+ "because the task is scheduled ASAP (start-to-finish) or " +
513
+ "has a fixed #{dependency.onEnd ? 'end' : 'start'} date.")
498
514
  end
499
515
  end
500
516
  end
@@ -543,7 +559,7 @@ class TaskJuggler
543
559
  # Check if we have been here before on this path.
544
560
  if path.include?([ @property, atEnd ])
545
561
  warning('loop_detected',
546
- "Loop detected at #{atEnd ? 'end' : 'start'} " +
562
+ "Dependency loop detected at #{atEnd ? 'end' : 'start'} " +
547
563
  "of task #{@property.fullId}", false)
548
564
  skip = true
549
565
  path.each do |t, e|
@@ -1145,8 +1161,15 @@ class TaskJuggler
1145
1161
  end
1146
1162
 
1147
1163
  def query_complete(query)
1148
- query.sortable = query.numerical = complete = a('complete').to_i
1149
- query.string = "#{complete}%"
1164
+ # If we haven't calculated the value yet, calculate it first.
1165
+ unless (complete = a('complete'))
1166
+ calcCompletion
1167
+ complete = a('complete')
1168
+ end
1169
+
1170
+ query.sortable = query.numerical = complete
1171
+ # For the string output, we only use integer numbers.
1172
+ query.string = "#{complete.to_i}%"
1150
1173
  end
1151
1174
 
1152
1175
  # Compute the cost generated by this Task for a given Account during a given
@@ -1278,8 +1301,11 @@ class TaskJuggler
1278
1301
  # the report time frame.
1279
1302
  def query_resources(query)
1280
1303
  list = ''
1304
+ return list unless @property.leaf?
1305
+
1281
1306
  a('assignedresources').each do |resource|
1282
- if getAllocatedTime(query.startIdx, query.endIdx, resource) > 0.0
1307
+ if resource.allocated?(@scenarioIdx,
1308
+ Interval.new(query.start, query.end), @property)
1283
1309
  list += ', ' unless list.empty?
1284
1310
  list += "#{resource.name} (#{resource.fullId})"
1285
1311
  end
@@ -1303,6 +1329,16 @@ class TaskJuggler
1303
1329
  end
1304
1330
  end
1305
1331
 
1332
+ def query_status(query)
1333
+ # If we haven't calculated the completion yet, calculate it first.
1334
+ if (status = a('status')).empty?
1335
+ calcCompletion
1336
+ status = a('status')
1337
+ end
1338
+
1339
+ query.string = status
1340
+ end
1341
+
1306
1342
  def query_targets(query)
1307
1343
  targetList = PropertyList.new(@project.tasks, false)
1308
1344
  targets(targetList, true)
@@ -1324,7 +1360,12 @@ class TaskJuggler
1324
1360
  # Compute the total time _resource_ or all resources are allocated during
1325
1361
  # interval specified by _startIdx_ and _endIdx_.
1326
1362
  def getAllocatedTime(startIdx, endIdx, resource = nil)
1327
- return 0.0 if a('milestone')
1363
+ return 0.0 if a('milestone') || startIdx >= endIdx ||
1364
+ (resource && !a('assignedresources').include?(resource))
1365
+
1366
+ key = [ self, :TaskScenarioAllocatedTime, startIdx, endIdx, resource ].hash
1367
+ allocatedTime = @dCache.load(key)
1368
+ return allocatedTime if allocatedTime
1328
1369
 
1329
1370
  allocatedTime = 0.0
1330
1371
  if @property.container?
@@ -1344,14 +1385,19 @@ class TaskJuggler
1344
1385
  end
1345
1386
  end
1346
1387
  end
1347
- allocatedTime
1388
+ @dCache.store(allocatedTime, key)
1348
1389
  end
1349
1390
 
1350
1391
  # Compute the effective work a _resource_ or all resources do during the
1351
1392
  # interval specified by _startIdx_ and _endIdx_. The effective work is the
1352
1393
  # actual work multiplied by the efficiency of the resource.
1353
1394
  def getEffectiveWork(startIdx, endIdx, resource = nil)
1354
- return 0.0 if a('milestone')
1395
+ return 0.0 if a('milestone') || startIdx >= endIdx ||
1396
+ (resource && !a('assignedresources').include?(resource))
1397
+
1398
+ key = [ self, :TaskScenarioEffectiveWork, startIdx, endIdx, resource ].hash
1399
+ workLoad = @dCache.load(key)
1400
+ return workLoad if workLoad
1355
1401
 
1356
1402
  workLoad = 0.0
1357
1403
  if @property.container?
@@ -1370,7 +1416,7 @@ class TaskJuggler
1370
1416
  end
1371
1417
  end
1372
1418
  end
1373
- workLoad
1419
+ @dCache.store(workLoad, key)
1374
1420
  end
1375
1421
 
1376
1422
  # Return a list of intervals that lay within _iv_ and are at least
@@ -1435,6 +1481,8 @@ class TaskJuggler
1435
1481
  # Returns true of the _resource_ is assigned to this task or any of its
1436
1482
  # children.
1437
1483
  def hasResourceAllocated?(interval, resource)
1484
+ return false unless a('assignedresources').include?(resource)
1485
+
1438
1486
  if @property.leaf?
1439
1487
  return resource.allocated?(@scenarioIdx, interval, @property)
1440
1488
  else
@@ -1701,6 +1749,7 @@ class TaskJuggler
1701
1749
  # specified Interval.
1702
1750
  def bookBookings
1703
1751
  scheduled = a('scheduled')
1752
+ slotDuration = @project['scheduleGranularity']
1704
1753
  a('booking').each do |booking|
1705
1754
  unless booking.resource.leaf?
1706
1755
  error('booking_resource_not_leaf',
@@ -1711,7 +1760,6 @@ class TaskJuggler
1711
1760
  error('booking_forward_only',
1712
1761
  "Only forward scheduled tasks may have booking statements.")
1713
1762
  end
1714
- slotDuration = @project['scheduleGranularity']
1715
1763
  booking.intervals.each do |interval|
1716
1764
  startIdx = @project.dateToIdx(interval.start)
1717
1765
  date = interval.start
@@ -1727,8 +1775,7 @@ class TaskJuggler
1727
1775
  # set to the begining of the first booked slot. The lastSlot
1728
1776
  # will be set to the last booked slot
1729
1777
  @lastSlot = date if @lastSlot.nil? || date > @lastSlot
1730
- @tentativeEnd = tEnd if @tentativeEnd.nil? ||
1731
- @tentativeEnd < tEnd
1778
+ @tentativeEnd = tEnd if @tentativeEnd.nil? || @tentativeEnd < tEnd
1732
1779
  if !scheduled && (a('start').nil? || date < a('start'))
1733
1780
  @property['start', @scenarioIdx] = date
1734
1781
  end
@@ -1737,7 +1784,7 @@ class TaskJuggler
1737
1784
  @property['assignedresources', @scenarioIdx] << booking.resource
1738
1785
  end
1739
1786
  end
1740
- if a('length') > 0 && @project.isWorkingTime(date, tEnd)
1787
+ if a('length') > 0 && @project.isWorkingTime(date)
1741
1788
  # For tasks with a 'length' we track the covered work time and
1742
1789
  # set the task to 'scheduled' when we have enough length.
1743
1790
  @doneLength += 1
data/lib/TextParser.rb CHANGED
@@ -19,13 +19,11 @@ require 'Log'
19
19
 
20
20
  class TaskJuggler
21
21
 
22
- # The TextParser implements a regular LALR parser. But it uses a recursive
23
- # rule traversor instead of the more commonly found state machine generated by
24
- # yacc-like tools. Since stack depths is not really an issue with a Ruby
25
- # implementation this approach has one big advantage. The syntax of the parser
26
- # can be modified during parsing. This allows support for languages that can
27
- # extend themself. The TaskJuggler syntax is such an beast. Traditional yacc
28
- # generated parsers would fail with such a syntax.
22
+ # The TextParser implements a somewhat modified LL(1) parser. It uses a
23
+ # dynamically compiled state machine. Dynamically means, that the syntax can
24
+ # be extended during the parse process. This allows support for languages
25
+ # that can extend their syntax during the parse process. The TaskJuggler
26
+ # syntax is such an beast.
29
27
  #
30
28
  # This class is just a base class. A complete parser would derive from this
31
29
  # class and implement the rule set and the functions _nextToken()_ and
@@ -39,12 +37,21 @@ class TaskJuggler
39
37
  # document a pattern, the functions TextParser#doc, TextParser#descr,
40
38
  # TextParser#also and TextParser#arg can be used.
41
39
  #
40
+ # In contrast to conventional LL grammars, we use a slightly improved syntax
41
+ # descriptions. Repeated patterns are not described by recursive call but we
42
+ # use a repeat flag for syntax rules that consists of repeatable patterns.
43
+ # This removes the need for recursion elimination when compiling the state
44
+ # machine and makes the syntax a lot more readable. However, it adds a bit
45
+ # more complexity to the state machine. Optional patterns are described by
46
+ # a rule flag, not by adding an empty pattern.
47
+ #
42
48
  # To start parsing the input the function TextParser#parse needs to be called
43
49
  # with the name of the start rule.
44
50
  class TextParser
45
51
 
46
52
  # Utility class so that we can distinguish Array results from the Array
47
- # containing the results of a repeatable rule.
53
+ # containing the results of a repeatable rule. We define some merging
54
+ # method with a slightly different behaviour.
48
55
  class TextParserResultArray < Array
49
56
 
50
57
  def initialize
@@ -62,7 +69,6 @@ class TaskJuggler
62
69
  super
63
70
  end
64
71
  end
65
-
66
72
  end
67
73
 
68
74
  attr_reader :rules, :messageHandler
@@ -76,9 +82,15 @@ class TaskJuggler
76
82
  # Array to hold the token types that the scanner can return.
77
83
  @variables = []
78
84
  # An list of token types that are not allowed in the current context.
79
- @badVariables = []
85
+ # For performance reasons we use a hash with the token as key. The value
86
+ # is irrelevant.
87
+ @blockedVariables = {}
80
88
  # The currently processed rule.
81
89
  @cr = nil
90
+
91
+ @states = {}
92
+ # The stack used by the FSM.
93
+ @stack = nil
82
94
  end
83
95
 
84
96
  # Limit the allowed tokens of the scanner to the subset passed by the
@@ -86,8 +98,13 @@ class TaskJuggler
86
98
  def limitTokenSet(tokenSet)
87
99
  return unless tokenSet
88
100
 
89
- @badVariables = @variables.dup
90
- @badVariables.delete_if { |v| tokenSet.include?(v) }
101
+ # Create a copy of all supported variables.
102
+ blockedVariables = @variables.dup
103
+ # Then delete all that are in the limited set.
104
+ blockedVariables.delete_if { |v| tokenSet.include?(v) }
105
+ # And convert the list into a Hash for faster lookups.
106
+ @blockedVariables = {}
107
+ blockedVariables.each { |v| @blockedVariables[v] = true }
91
108
  end
92
109
 
93
110
  # Call all methods that start with 'rule_' to initialize the rules.
@@ -108,6 +125,8 @@ class TaskJuggler
108
125
  # TextParser#repeatable will then implicitely operate on the most recently
109
126
  # added rule.
110
127
  def newRule(name)
128
+ # Use a symbol instead of a String.
129
+ name = name.intern
111
130
  raise "Fatal Error: Rule #{name} already exists" if @rules.has_key?(name)
112
131
 
113
132
  if block_given?
@@ -148,13 +167,26 @@ class TaskJuggler
148
167
  end
149
168
 
150
169
  # This function needs to be called whenever new rules or patterns have been
151
- # added and before the next call to TextParser#parse.
170
+ # added and before the next call to TextParser#parse. It's perfectly ok to
171
+ # call this function from within a parse() call as long as the states that
172
+ # are currently on the stack have not been modified.
152
173
  def updateParserTables
153
- @rules.each_value { |rule| rule.transitions = {} }
174
+ saveFsmStack
175
+ # Invalidate some cached data.
176
+ @rules.each_value { |rule| rule.flushCache }
177
+ @states = {}
178
+ # Generate the parser states for all patterns of all rules.
154
179
  @rules.each_value do |rule|
155
- getTransitions(rule)
180
+ rule.generateStates.each do |s|
181
+ @states[[ s.rule, s.pattern, s.index ]] = s
182
+ end
156
183
  checkRule(rule)
157
184
  end
185
+ # Compute the transitions between the generated states.
186
+ @states.each_value do |state|
187
+ state.addTransitions(@states, @rules)
188
+ end
189
+ restoreFsmStack
158
190
  end
159
191
 
160
192
  # To parse the input this function needs to be called with the name of the
@@ -164,9 +196,8 @@ class TaskJuggler
164
196
  def parse(ruleName)
165
197
  @stack = []
166
198
  @@expectedTokens = []
167
- updateParserTables
168
199
  begin
169
- result = parseRuleR(@rules[ruleName])
200
+ result = parseFSM(@rules[ruleName])
170
201
  rescue TjException => msg
171
202
  if msg.message && !msg.message.empty?
172
203
  @messageHandler.critical('parse', msg.message)
@@ -181,17 +212,8 @@ class TaskJuggler
181
212
  # currently processed TextParser::Rule. Or return nil if we don't have a
182
213
  # current position.
183
214
  def sourceFileInfo
184
- return nil if @stack.nil? || @stack.empty?
185
- @stack.last.sourceFileInfo[0]
186
- end
187
-
188
- def matchingRules(keyword)
189
- matches = []
190
- @rules.each do |name, rule|
191
- patIdx = rule.matchingPatternIndex('_' + keyword)
192
- matches << [ rule, patIdx ] if patIdx
193
- end
194
- matches
215
+ return @scanner.sourceFileInfo if @stack.nil? || @stack.length <= 1
216
+ @stack.last.firstSourceFileInfo
195
217
  end
196
218
 
197
219
  def error(id, text, sfi = nil, data = nil)
@@ -218,79 +240,21 @@ class TaskJuggler
218
240
 
219
241
  private
220
242
 
221
- # getTransitions recursively determines all possible target tokens
222
- # that the _rule_ matches. A target token can either be a fixed token
223
- # (prefixed with _), a variable token (prefixed with $) or an end token
224
- # (just a .). The list of found target tokens is stored in the _transitions_
225
- # list of the rule. For each rule pattern we store the transitions for this
226
- # pattern in a token -> rule hash.
227
- def getTransitions(rule)
228
- # If we have processed this rule before we can just return a copy
229
- # of the transitions of this rule. This avoids endless recursions.
230
- return rule.transitions.dup unless rule.transitions.empty?
231
-
232
- rule.transitions = []
233
- rule.patterns.each do |pat|
234
- allTokensOptional = true
235
- transitions = { }
236
- pat.each do |token|
237
- tokenId = token[1..-1]
238
- if token[0] == ?!
239
- unless @rules.has_key?(tokenId)
240
- raise "Fatal Error: Unknown reference to #{tokenId} in pattern " +
241
- "#{pat} + of rule #{rule.name}"
242
- end
243
- refRule = @rules[tokenId]
244
- # If the referenced rule describes optional content, we need to look
245
- # at the next token as well.
246
- res = getTransitions(@rules[tokenId])
247
- allTokensOptional = false unless refRule.optional?(@rules)
248
- # Combine the hashes for each pattern into a single hash
249
- res.each do |pat_i|
250
- pat_i.each { |tok, r| transitions[tok] = r }
251
- end
252
- elsif '_$.'.include?(token[0])
253
- transitions[token] = rule
254
- allTokensOptional = false
255
- else
256
- raise 'Fatal Error: Illegal token type specifier used for token' +
257
- ": #{tokenId}"
258
- end
259
- break unless allTokensOptional
260
- end
261
- # Make sure that we only have one possible transition for each
262
- # target.
263
- transitions.each do |key, value|
264
- rule.transitions.each do |trans|
265
- if trans.has_key?(key)
266
- rule.dump
267
- raise "Fatal Error: Rule #{rule.name} has ambiguous " +
268
- "transitions for target #{key}"
269
- end
270
- end
271
- end
272
- rule.transitions << transitions
273
- end
274
- rule.transitions.dup
275
- end
276
-
277
243
  def checkRule(rule)
278
244
  if rule.patterns.empty?
279
245
  raise "Rule #{rule.name} must have at least one pattern"
280
246
  end
281
247
 
282
248
  rule.patterns.each do |pat|
283
- pat.each do |tok|
284
- type = tok[0]
285
- token = tok[1..-1]
286
- if type == ?$
287
- if @variables.index(token).nil?
249
+ pat.each do |type, name|
250
+ if type == :variable
251
+ if @variables.index(name).nil?
288
252
  error('unsupported_token',
289
- "The token #{token} is not supported here.", token[2])
253
+ "The token #{name} is not supported here.")
290
254
  end
291
- elsif type == ?!
292
- if @rules[token].nil?
293
- raise "Fatal Error: Reference to unknown rule #{token} in " +
255
+ elsif type == :reference
256
+ if @rules[name].nil?
257
+ raise "Fatal Error: Reference to unknown rule #{name} in " +
294
258
  "pattern '#{pat}' of rule #{rule.name}"
295
259
  end
296
260
  end
@@ -298,235 +262,192 @@ class TaskJuggler
298
262
  end
299
263
  end
300
264
 
301
- # This function processes the input starting with the syntax description of
302
- # _rule_. It recursively calls this function whenever the syntax description
303
- # contains the reference to another rule.
304
- # This recursive version has cleaner code and is about 8% faster than
305
- # parseRuleNR.
306
- def parseRuleR(rule)
307
- #Log.enter('parseRuleR', "Parsing with rule #{rule.name}")
308
- #puts "Parsing with rule #{rule.name}"
309
- result = rule.repeatable ? TextParserResultArray.new : nil
310
- # Rules can be marked 'repeatable'. This flag will be set to true after
311
- # the first iteration has been completed.
312
- repeatMode = false
265
+ def parseFSM(rule)
266
+ unless (state = @states[[ rule, nil, 0 ]])
267
+ error("no_start_state", "No start state for rule #{rule.name} found")
268
+ end
269
+ @stack = [ TextParser::StackElement.new(nil, state) ]
270
+
313
271
  loop do
314
- # At the beginning of a rule we need a token from the input to determine
315
- # which pattern of the rule needs to be processed.
316
- token = getNextToken
317
-
318
- return result unless (pattern = findPattern(rule, token, repeatMode))
319
- # The @stack will store the resulting value of each element in the
320
- # pattern.
321
- @stack << TextParser::StackElement.new(pattern.function)
322
-
323
- pattern.each do |element|
324
- # Separate the type and token text for pattern element.
325
- elType = element[0]
326
- elToken = element[1..-1]
327
- if elType == ?!
328
- # The element is a reference to another rule. Return the token if
329
- # we still have one and continue with the referenced rule.
330
- unless token.nil?
331
- sfi = token[2]
332
- returnToken(token)
333
- token = nil
272
+ if state.transitions.empty?
273
+ # The final states of each pattern have no pre-compiled transitions.
274
+ # For such a state, we don't need to get a new token.
275
+ transition = token = nil
276
+ else
277
+ transition = state.transition(token = getNextToken)
278
+ end
279
+
280
+ if transition
281
+ # Shift: This for normal state transitions. This may be from one
282
+ # token of a pattern to the next token of the same pattern, to the
283
+ # start of a new pattern or a loop-back to the start of a pattern of
284
+ # the same rule. The transition tells us what state we have to
285
+ # process next.
286
+ state = transition.state
287
+
288
+ # If we have looped-back we need to finish the pattern first. Final
289
+ # tokens of repeatable rules do have transitions!
290
+ finishPattern(token) if transition.loopBack
291
+
292
+ # Transitions that enter rules generate states which we need to
293
+ # resume at when a rule has been completely processed. We push this
294
+ # list of states on the @stack.
295
+ stackElement = @stack.last
296
+ first = true
297
+ transition.stateStack.each do |s|
298
+ if first && s.pattern == stackElement.state.pattern
299
+ # The first state in the list may just be another state of the
300
+ # current pattern. In this case, we already have the
301
+ # StackElement on the @stack. We only need to update the State
302
+ # for the current StackElement.
303
+ stackElement.state = s
334
304
  else
335
- sfi = nil
305
+ # For other patterns, we just push a new StackElement onto the
306
+ # @stack.
307
+ @stack.push(TextParser::StackElement.new(nil, s))
336
308
  end
337
- @stack.last.store(parseRuleR(@rules[elToken]), sfi)
338
- #Log << "Resuming rule #{rule.name}"
339
- #puts "Resuming rule #{rule.name}"
340
- else
341
- # In case the element is a keyword or variable we have to get a new
342
- # token if we don't have one anymore.
343
- token = getNextToken unless token
344
-
345
- processNormalElements(elType, elToken, token)
309
+ first = false
310
+ end
346
311
 
347
- # The token has been consumed. Reset the variable.
348
- token = nil
349
- @@expectedTokens = []
312
+ if state.index == 0
313
+ # If we have just started with a new pattern (or loop-ed back) we
314
+ # need to push a new StackEntry onto the @stack. The StackEntry
315
+ # stores the result of the pattern and keeps the State that we
316
+ # need to return to in case we jump to other patterns from this
317
+ # pattern.
318
+ function = state.index == state.pattern.tokens.length - 1 ?
319
+ state.pattern.function : nil
320
+ @stack.push(TextParser::StackElement.new(state.pattern.function,
321
+ state))
350
322
  end
351
- end
352
323
 
353
- # Once the complete pattern has been processed we call the processing
354
- # function for this pattern to operate on the value array. Then pop the
355
- # entry for this rule from the stack.
356
- @val = @stack.last.val
357
- @sourceFileInfo = @stack.last.sourceFileInfo
358
- res = nil
359
- res = @stack.last.function.call unless @stack.last.function.nil?
360
- @stack.pop
361
-
362
- # If the rule is not repeatable we can store the result and break the
363
- # outer loop to exit the function.
364
- unless rule.repeatable
365
- result = res
366
- break
324
+ # Store the token value in the result Array.
325
+ @stack.last.insert(state.index, token[1], token[2], false)
326
+ else
327
+ # Reduce: We've reached the end of a rule. There is no pre-compiled
328
+ # transition available. The current token, if we have one, is of no
329
+ # use to us during this state. We just return it to the scanner. The
330
+ # next state is determined by the first matching state from the
331
+ # @stack.
332
+ if state.noReduce
333
+ # Only states that finish a rule may trigger a reduce operation.
334
+ # Other states have the noReduce flag set. If a reduce for such a
335
+ # state is triggered, we found a token that is not supported by
336
+ # the syntax rules.
337
+ error("no_reduce",
338
+ "Unexpected token '#{token[1]}' found. " +
339
+ "Expecting one of " +
340
+ "#{@stack.last.state.expectedTokens.join(', ')}")
341
+ end
342
+ returnToken(token) if token
343
+ if finishPattern(token)
344
+ # Accept: We're done with parsing.
345
+ break
346
+ end
347
+ state = @stack.last.state
367
348
  end
368
-
369
- # Otherwise we append the result to the result array and turn repeat
370
- # mode on.
371
- result << res
372
- repeatMode = true
373
349
  end
374
350
 
375
- #Log.exit('parseRuleR', "Finished rule #{rule.name}")
376
- #puts "Finished rule #{rule.name}"
377
- return result
351
+ @stack[0].val[0]
378
352
  end
379
353
 
380
- # This function processes the input starting with the syntax description
381
- # of _rule_. It's implemented as an unrolled recursion. It recursively
382
- # iterates over the rule tree as controlled by the input file.
383
- # This version is not limited by the size of the system stack. So far, I'm
384
- # not aware of any project that is too large for the system stack. Since
385
- # the recursive version parseRuleR is about 8% faster and has cleaner
386
- # code, we use that by default.
387
- def parseRuleNR(rule)
388
- elementIdx = 0
389
- recursionResult = nil
390
- # These flags are used to managed the control flow to and from the
391
- # recursion point.
392
- recur = resume = false
393
- # The stack that holds the context for the recursion levels. It's either
394
- # just a rule to start a new recursion or an Array of state variables.
395
- recursionStack = [ rule ]
396
- begin
397
- # Pop the top entry from the recursion stack.
398
- se = recursionStack.pop
399
- if se.is_a?(Array)
400
- # We have essentially finished a recursion level and need to get
401
- # back to the place where we started the recursion. First, we need
402
- # to restore the state again.
403
- rule, pattern, elementIdx, result, repeatMode, sfi = se
404
- #Log << "Recursion loop started in resume mode for rule #{rule.name}"
405
- # Now jump to the recursion point without doing anything else.
406
- resume = true
407
- else
408
- # Start a new recursion level. The rule tells us how to interpret
409
- # the input text.
410
- rule = se
411
- #Log.enter('parseRuleNR', "Parsing with rule #{rule.name}")
412
- resume = false
413
- end
414
-
415
- unless resume
416
- result = rule.repeatable ? TextParserResultArray.new : nil
417
- # Rules can be marked 'repeatable'. This flag will be set to true
418
- # after the first iteration has been completed.
419
- repeatMode = false
354
+ def finishPattern(token)
355
+ #dumpStack
356
+ # To finish a pattern we need to pop the StackElement with the token
357
+ # values from the stack.
358
+ stackEntry = @stack.pop
359
+ if stackEntry.nil? || @stack.empty?
360
+ # Check if we have reached the bottom of the stack.
361
+ token = getNextToken unless token
362
+ if token[0] == :endOfText
363
+ # If the token is the end of the top-level file, we're done. We push
364
+ # back the StackEntry since it holds the overall result of the
365
+ # parsing.
366
+ @stack.push(stackEntry)
367
+ return true
420
368
  end
369
+ # If it's not the EOF token, we found a token that violates the syntax
370
+ # rules.
371
+ error('unexpctd_token', "Unexpected token '#{token[1]}' found. " +
372
+ "Expecting one of " +
373
+ "#{stackEntry.state.expectedTokens.join(', ')}")
374
+ end
375
+ # Memorize if the rule for this pattern was repeatable. Then we will
376
+ # store the result of the pattern in an Array.
377
+ ruleIsRepeatable = stackEntry.state.rule.repeatable
378
+
379
+ state = stackEntry.state
380
+ result = nil
381
+ if state.pattern.function
382
+ # Make the token values and their SourceFileInfo available.
383
+ @val = stackEntry.val
384
+ @sourceFileInfo = stackEntry.sourceFileInfo
385
+ # Now call the pattern action to compute the value of the pattern.
386
+ result = state.pattern.function.call
387
+ end
421
388
 
422
- loop do
423
- unless resume
424
- # At the beginning of a rule we need a token from the input to
425
- # determine which pattern of the rule needs to be processed.
426
- token = getNextToken
427
-
428
- break unless (pattern = findPattern(rule, token, repeatMode))
429
- # The @stack will store the resulting value of each element in the
430
- # pattern.
431
- @stack << TextParser::StackElement.new(pattern.function)
432
-
433
- # Once we've found the right pattern, we need to process each
434
- # element.
435
- elementIdx = 0
436
- end
437
-
438
- elementCount = pattern.length
439
- while elementIdx < elementCount
440
- element = pattern[elementIdx]
441
- # Separate the type and token text for pattern element.
442
- elType = element[0]
443
- elToken = element[1..-1]
444
- if elType == ?!
445
- unless resume
446
- # The element is a reference to another rule. Return the token
447
- # if we still have one and continue with the referenced rule.
448
- if token
449
- sfi = token[2]
450
- returnToken(token)
451
- token = nil
452
- else
453
- sfi = nil
454
- end
455
- # This is where the recursion would happen. Instead, we push
456
- # the state variables and then the next rule onto the
457
- # recursion stack.
458
- recursionStack.push([ rule, pattern, elementIdx, result,
459
- repeatMode, sfi ])
460
- recursionStack.push(@rules[elToken])
461
- # Now terminate all but the outer loops without doing anything
462
- # else.
463
- recur = true
464
- break
465
- else
466
- # We're back right after where the recursion started. Store
467
- # the result and turn resume mode off again.
468
- @stack.last.store(recursionResult, sfi)
469
- resume = false
470
- end
471
- else
472
- # In case the element is a keyword or variable we have to get a
473
- # new token if we don't have one anymore.
474
- token = getNextToken unless token
475
-
476
- processNormalElements(elType, elToken, token)
389
+ # We use the SourceFileInfo of the first token of the pattern to store
390
+ # it with the result of the pattern.
391
+ firstSourceFileInfo = stackEntry.firstSourceFileInfo
392
+ # Store the result at the correct position into the next lower level of
393
+ # the stack.
394
+ stackEntry = @stack.last
395
+ stackEntry.insert(stackEntry.state.index, result,
396
+ firstSourceFileInfo, ruleIsRepeatable)
397
+ false
398
+ end
477
399
 
478
- # The token has been consumed. Reset the variable.
479
- token = nil
480
- @@expectedTokens = []
400
+ def dumpStack
401
+ #puts "Stack level #{@stack.length}"
402
+ @stack.each do |sl|
403
+ print "#{@stack.index(sl)}: "
404
+ sl.each do |v|
405
+ if v.is_a?(Array)
406
+ begin
407
+ print "[#{v.join('|')}]|"
408
+ rescue
409
+ print "[#{v[0].class}...]|"
410
+ end
411
+ else
412
+ begin
413
+ print "#{v}|"
414
+ rescue
415
+ print v.class
481
416
  end
482
- elementIdx += 1
483
- end # of pattern while loop
484
-
485
- # Skip the rest of the loop in recur mode.
486
- break if recur
487
-
488
- elementIdx = 0
489
-
490
- # Once the complete pattern has been processed we call the
491
- # processing function for this pattern to operate on the value
492
- # array. Then pop the entry for this rule from the stack. The
493
- # called function will use @val and @sourceFileInfo to retrieve
494
- # data from the parser.
495
- @val = @stack.last.val
496
- @sourceFileInfo = @stack.last.sourceFileInfo
497
- res = @stack.last.function ? @stack.last.function.call : nil
498
- @stack.pop
499
-
500
- # If the rule is not repeatable we can store the result and break
501
- # the outer loop to exit the function.
502
- unless rule.repeatable
503
- result = res
504
- break
505
417
  end
418
+ end
419
+ print " -> #{sl.state ? sl.state.to_s(true) : 'nil'} #{sl.function.nil? ? '' : '(Called)'}"
420
+ puts ""
421
+ end
422
+ end
506
423
 
507
- # Otherwise we append the result to the result array and turn repeat
508
- # mode on.
509
- result << res
510
- # We have completed the first iteration. Set the repeat mode flag to
511
- # indicate that further iterations are already re-runs.
512
- repeatMode = true
513
- end # of rule processing loop
424
+ # Convert the FSM stack state entries from State objects into [ rule,
425
+ # pattern, index ] equivalents.
426
+ def saveFsmStack
427
+ return unless @stack
514
428
 
515
- if recur
516
- recur = false
517
- else
518
- #Log.exit('parseRuleNR', "Finished rule #{rule.name}")
519
- recursionResult = result
520
- end
521
- end while !recursionStack.empty?
429
+ @stack.each do |s|
430
+ next unless (st = s.state)
431
+ s.state = [ st.rule, st.pattern, st.index ]
432
+ end
433
+ end
434
+
435
+ # Convert the FSM stack state entries from [ rule, pattern, index ] into
436
+ # the respective State objects again.
437
+ def restoreFsmStack
438
+ return unless @stack
522
439
 
523
- return result
440
+ @stack.each do |s|
441
+ next unless (state = @states[s.state])
442
+ raise "Stack restore failed. Cannot find state" unless state
443
+ s.state = state
444
+ end
524
445
  end
525
446
 
526
447
  def getNextToken
527
448
  token = nextToken
528
449
  #Log << "Token: [#{token[0]}][#{token[1]}]"
529
- if @badVariables.include?(token[0])
450
+ if @blockedVariables[token[0]]
530
451
  error('unsupported_token',
531
452
  "The token #{token[1]} is not supported in this context.",
532
453
  token[2])
@@ -534,92 +455,6 @@ class TaskJuggler
534
455
  token
535
456
  end
536
457
 
537
- def findPattern(rule, token, repeatMode)
538
- # The scanner cannot differentiate between keywords and identifiers. So
539
- # whenever an identifier is returned we have to see if we have a
540
- # matching keyword first. If none is found, then look for normal
541
- # identifiers.
542
- if token[0] == 'ID'
543
- if (patIdx = rule.matchingPatternIndex('_' + token[1])).nil?
544
- patIdx = rule.matchingPatternIndex("$ID")
545
- end
546
- elsif token[0] == 'LITERAL'
547
- patIdx = rule.matchingPatternIndex('_' + token[1])
548
- elsif token[0] == false
549
- patIdx = rule.matchingPatternIndex('.')
550
- else
551
- patIdx = rule.matchingPatternIndex('$' + token[0])
552
- end
553
-
554
- # If no matching pattern is found for the token we have to check if the
555
- # rule is optional or we are in repeat mode. If this is the case, return
556
- # the token back to the scanner. Otherwise, we have found a token we
557
- # cannot handle at this point.
558
- if patIdx.nil?
559
- # Append the list of expected tokens to the @@expectedToken array.
560
- # This may be used in a later rule to provide more details when an
561
- # error occured.
562
- rule.transitions.each do |transition|
563
- keys = transition.keys
564
- keys.collect! { |key| key[1..-1] }
565
- @@expectedTokens += keys
566
- @@expectedTokens.sort!
567
- end
568
-
569
- unless rule.optional?(@rules) || repeatMode
570
- error('unexpctd_token',
571
- (token[0] != false ?
572
- "Unexpected token '#{token[1]}' of type " +
573
- "'#{token[0]}'. " :
574
- "Unexpected end of file in #{@scanner.fileName}. ") +
575
- (@@expectedTokens.length > 1 ?
576
- "Expecting one of #{@@expectedTokens.join(', ')}" :
577
- "Expecting #{@@expectedTokens[0]}"), token[2])
578
- end
579
- returnToken(token)
580
- return nil
581
- end
582
-
583
- rule.pattern(patIdx)
584
- end
585
-
586
- # Handle the elements that don't trigger a recursion.
587
- def processNormalElements(elType, elToken, token)
588
- if elType == ?_
589
- # If the element requires a keyword the token must match this
590
- # keyword.
591
- if elToken != token[1]
592
- text = "'#{elToken}' expected but found " +
593
- "'#{token[1]}' (#{token[0]})."
594
- unless @@expectedTokens.empty?
595
- text = "#{@@expectedTokens.join(', ')} or " + text
596
- end
597
- error('spec_keywork_expctd', text, token[2])
598
- end
599
- @stack.last.store(elToken, token[2])
600
- elsif elType == ?.
601
- if token[0..1] != [ '.', '<END>' ]
602
- error('end_expected',
603
- "Found garbage at expected end of text: #{token[1]}\n" +
604
- "If you see this in the middle of your text, you probably " +
605
- "have closed your context too early.", token[2])
606
- end
607
- else
608
- # The token must match the expected variable type.
609
- if token[0] != elToken
610
- text = "'#{elToken}' expected but found " +
611
- "'#{token[1]}' (#{token[0]})."
612
- unless @@expectedTokens.empty?
613
- text = "#{@@expectedTokens.join(', ')} or " + text
614
- end
615
- error('spec_token_expctd', text, token[2])
616
- end
617
- # If the element is a variable store the value of the token.
618
- @stack.last.store(token[1], token[2])
619
- end
620
- end
621
-
622
458
  end
623
459
 
624
460
  end
625
-