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.
- data/CHANGELOG +119 -0
- data/benchmarks/allocatedSlots.tjp +1602 -0
- data/benchmarks/booking.tjp +40 -30
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/css/tjmanual.css +0 -0
- data/{test/TestSuite/Scheduler/Correct → benchmarks}/css/tjreport.css +1 -0
- data/benchmarks/gantt.tjp +57 -0
- data/benchmarks/htmltaskreport.tjp +26 -1
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/details.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/flag-green.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/flag-red.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/flag-yellow.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/resource.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/resourcegroup.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/task.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/taskgroup.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/trend-down.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/trend-flat.png +0 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/icons/trend-up.png +0 -0
- data/benchmarks/profile.clt +36082 -0
- data/benchmarks/profile.html +58182 -0
- data/benchmarks/runbench.rb +6 -0
- data/{test/TestSuite/ReportGenerator/Errors → benchmarks}/scripts/wz_tooltip.js +0 -0
- data/doc/AppConfig.html +85 -37
- data/doc/Arguments.html +11 -1
- data/doc/CHANGELOG.html +131 -2
- data/doc/COPYING.html +11 -1
- data/doc/Object.html +12 -3
- data/doc/README.html +11 -1
- data/doc/RuntimeConfig.html +11 -1
- data/doc/String.html +11 -1
- data/doc/StringIO.html +11 -1
- data/doc/TaskJuggler.html +250 -219
- data/doc/TaskJuggler/Account.html +11 -1
- data/doc/TaskJuggler/AccountAttribute.html +11 -1
- data/doc/TaskJuggler/AccountScenario.html +11 -1
- data/doc/TaskJuggler/Allocation.html +11 -1
- data/doc/TaskJuggler/AllocationAttribute.html +11 -1
- data/doc/TaskJuggler/AttributeBase.html +11 -1
- data/doc/TaskJuggler/AttributeDefinition.html +11 -1
- data/doc/TaskJuggler/BatchProcessor.html +11 -1
- data/doc/TaskJuggler/Booking.html +11 -1
- data/doc/TaskJuggler/BookingListAttribute.html +11 -1
- data/doc/TaskJuggler/BooleanAttribute.html +11 -1
- data/doc/TaskJuggler/CSVFile.html +12 -2
- data/doc/TaskJuggler/CellSettingPattern.html +11 -1
- data/doc/TaskJuggler/CellSettingPatternList.html +11 -1
- data/doc/TaskJuggler/Charge.html +11 -1
- data/doc/TaskJuggler/ChargeListAttribute.html +11 -1
- data/doc/TaskJuggler/ChargeSet.html +11 -1
- data/doc/TaskJuggler/ChargeSetListAttribute.html +11 -1
- data/doc/TaskJuggler/CollisionDetector.html +1063 -0
- data/doc/TaskJuggler/ColumnListAttribute.html +11 -1
- data/doc/TaskJuggler/ColumnTable.html +11 -1
- data/doc/TaskJuggler/Daemon.html +11 -1
- data/doc/TaskJuggler/{OnShiftCache.html → DataCache.html} +127 -139
- data/doc/TaskJuggler/DataCacheEntry.html +711 -0
- data/doc/TaskJuggler/DateAttribute.html +11 -1
- data/doc/TaskJuggler/DefinitionListAttribute.html +11 -1
- data/doc/TaskJuggler/DependencyListAttribute.html +11 -1
- data/doc/TaskJuggler/DurationAttribute.html +16 -5
- data/doc/TaskJuggler/FileList.html +11 -1
- data/doc/TaskJuggler/FileRecord.html +11 -1
- data/doc/TaskJuggler/FixnumAttribute.html +19 -9
- data/doc/TaskJuggler/FlagListAttribute.html +29 -19
- data/doc/TaskJuggler/FloatAttribute.html +23 -13
- data/doc/TaskJuggler/FormatListAttribute.html +19 -9
- data/doc/TaskJuggler/GanttChart.html +94 -133
- data/doc/TaskJuggler/GanttContainer.html +11 -1
- data/doc/TaskJuggler/GanttHeader.html +11 -1
- data/doc/TaskJuggler/GanttHeaderScaleItem.html +11 -1
- data/doc/TaskJuggler/GanttLine.html +11 -1
- data/doc/TaskJuggler/GanttLoadStack.html +11 -1
- data/doc/TaskJuggler/GanttMilestone.html +11 -1
- data/doc/TaskJuggler/GanttRouter.html +247 -596
- data/doc/TaskJuggler/GanttTaskBar.html +11 -1
- data/doc/TaskJuggler/HTMLDocument.html +11 -1
- data/doc/TaskJuggler/HTMLGraphics.html +11 -1
- data/doc/TaskJuggler/Interval.html +11 -1
- data/doc/TaskJuggler/IntervalListAttribute.html +33 -23
- data/doc/TaskJuggler/JobInfo.html +11 -1
- data/doc/TaskJuggler/Journal.html +11 -1
- data/doc/TaskJuggler/JournalEntry.html +11 -1
- data/doc/TaskJuggler/JournalEntryList.html +11 -1
- data/doc/TaskJuggler/KeywordArray.html +11 -1
- data/doc/TaskJuggler/KeywordDocumentation.html +16 -6
- data/doc/TaskJuggler/Limits.html +11 -1
- data/doc/TaskJuggler/Limits/Limit.html +11 -1
- data/doc/TaskJuggler/LimitsAttribute.html +24 -14
- data/doc/TaskJuggler/ListAttributeBase.html +11 -1
- data/doc/TaskJuggler/Log.html +11 -1
- data/doc/TaskJuggler/LogFile.html +11 -1
- data/doc/TaskJuggler/LogicalAttribute.html +11 -1
- data/doc/TaskJuggler/LogicalExpression.html +11 -1
- data/doc/TaskJuggler/LogicalExpressionAttribute.html +19 -9
- data/doc/TaskJuggler/LogicalFlag.html +11 -1
- data/doc/TaskJuggler/LogicalFunction.html +11 -1
- data/doc/TaskJuggler/LogicalOperation.html +11 -1
- data/doc/TaskJuggler/Macro.html +11 -1
- data/doc/TaskJuggler/MacroTable.html +11 -1
- data/doc/TaskJuggler/ManagerResponsibilities.html +11 -1
- data/doc/TaskJuggler/ManagerStatusRecord.html +11 -1
- data/doc/TaskJuggler/Message.html +11 -1
- data/doc/TaskJuggler/MessageHandler.html +11 -1
- data/doc/TaskJuggler/Navigator.html +12 -2
- data/doc/TaskJuggler/NavigatorElement.html +11 -1
- data/doc/TaskJuggler/NikuProject.html +11 -1
- data/doc/TaskJuggler/NikuReport.html +11 -1
- data/doc/TaskJuggler/NikuResource.html +11 -1
- data/doc/TaskJuggler/NodeListAttribute.html +17 -7
- data/doc/TaskJuggler/PlaceHolderCell.html +722 -0
- data/doc/TaskJuggler/ProcessIntercom.html +11 -1
- data/doc/TaskJuggler/ProcessIntercomIface.html +11 -1
- data/doc/TaskJuggler/Project.html +587 -500
- data/doc/TaskJuggler/ProjectBroker.html +11 -1
- data/doc/TaskJuggler/ProjectBrokerIface.html +11 -1
- data/doc/TaskJuggler/ProjectFileParser.html +205 -192
- data/doc/TaskJuggler/ProjectFileScanner.html +230 -207
- data/doc/TaskJuggler/ProjectRecord.html +11 -1
- data/doc/TaskJuggler/ProjectServer.html +11 -1
- data/doc/TaskJuggler/ProjectServerIface.html +11 -1
- data/doc/TaskJuggler/PropertyAttribute.html +19 -9
- data/doc/TaskJuggler/PropertyList.html +95 -83
- data/doc/TaskJuggler/PropertySet.html +11 -1
- data/doc/TaskJuggler/PropertyTreeNode.html +11 -1
- data/doc/TaskJuggler/Query.html +234 -232
- data/doc/TaskJuggler/RTFHandlers.html +11 -1
- data/doc/TaskJuggler/RTFNavigator.html +11 -1
- data/doc/TaskJuggler/RTFQuery.html +11 -1
- data/doc/TaskJuggler/RTFReport.html +11 -1
- data/doc/TaskJuggler/RTFReportLink.html +11 -1
- data/doc/TaskJuggler/RTFWithQuerySupport.html +11 -1
- data/doc/TaskJuggler/RealFormat.html +12 -2
- data/doc/TaskJuggler/RealFormatAttribute.html +15 -5
- data/doc/TaskJuggler/ReferenceAttribute.html +38 -28
- data/doc/TaskJuggler/Report.html +96 -113
- data/doc/TaskJuggler/ReportBase.html +161 -152
- data/doc/TaskJuggler/ReportContext.html +11 -1
- data/doc/TaskJuggler/ReportServer.html +59 -48
- data/doc/TaskJuggler/ReportServerIface.html +51 -41
- data/doc/TaskJuggler/ReportServerRecord.html +11 -1
- data/doc/TaskJuggler/ReportServlet.html +11 -1
- data/doc/TaskJuggler/ReportTable.html +46 -25
- data/doc/TaskJuggler/ReportTableCell.html +296 -275
- data/doc/TaskJuggler/ReportTableColumn.html +14 -4
- data/doc/TaskJuggler/ReportTableLegend.html +11 -1
- data/doc/TaskJuggler/ReportTableLine.html +19 -7
- data/doc/TaskJuggler/Resource.html +12 -2
- data/doc/TaskJuggler/ResourceListAttribute.html +40 -30
- data/doc/TaskJuggler/ResourceListRE.html +11 -1
- data/doc/TaskJuggler/ResourceScenario.html +708 -565
- data/doc/TaskJuggler/RichText.html +54 -36
- data/doc/TaskJuggler/RichTextAttribute.html +31 -21
- data/doc/TaskJuggler/RichTextDocument.html +11 -1
- data/doc/TaskJuggler/RichTextElement.html +11 -1
- data/doc/TaskJuggler/RichTextFunctionExample.html +11 -1
- data/doc/TaskJuggler/RichTextFunctionHandler.html +11 -1
- data/doc/TaskJuggler/RichTextImage.html +11 -1
- data/doc/TaskJuggler/RichTextIntermediate.html +81 -71
- data/doc/TaskJuggler/RichTextParser.html +88 -33
- data/doc/TaskJuggler/RichTextScanner.html +45 -35
- data/doc/TaskJuggler/RichTextSnip.html +11 -1
- data/doc/TaskJuggler/RichTextSyntaxRules.html +436 -389
- data/doc/TaskJuggler/Scenario.html +11 -1
- data/doc/TaskJuggler/ScenarioData.html +11 -1
- data/doc/TaskJuggler/ScenarioListAttribute.html +23 -13
- data/doc/TaskJuggler/Scoreboard.html +92 -73
- data/doc/TaskJuggler/SheetHandlerBase.html +11 -1
- data/doc/TaskJuggler/SheetReceiver.html +11 -1
- data/doc/TaskJuggler/SheetSender.html +11 -1
- data/doc/TaskJuggler/Shift.html +11 -1
- data/doc/TaskJuggler/ShiftAssignment.html +11 -1
- data/doc/TaskJuggler/ShiftAssignments.html +11 -1
- data/doc/TaskJuggler/ShiftAssignmentsAttribute.html +24 -14
- data/doc/TaskJuggler/ShiftScenario.html +11 -1
- data/doc/TaskJuggler/SimpleQueryExpander.html +11 -1
- data/doc/TaskJuggler/SortListAttribute.html +21 -11
- data/doc/TaskJuggler/SourceFileInfo.html +11 -1
- data/doc/TaskJuggler/StatusSheetReceiver.html +11 -1
- data/doc/TaskJuggler/StatusSheetReport.html +11 -1
- data/doc/TaskJuggler/StatusSheetSender.html +112 -11
- data/doc/TaskJuggler/StringAttribute.html +23 -13
- data/doc/TaskJuggler/SymbolAttribute.html +19 -9
- data/doc/TaskJuggler/SyntaxReference.html +80 -71
- data/doc/TaskJuggler/TOCEntry.html +11 -1
- data/doc/TaskJuggler/TSResourceRecord.html +11 -1
- data/doc/TaskJuggler/TSTaskRecord.html +11 -1
- data/doc/TaskJuggler/TableColumnDefinition.html +11 -1
- data/doc/TaskJuggler/TableOfContents.html +11 -1
- data/doc/TaskJuggler/TableReport.html +422 -411
- data/doc/TaskJuggler/Task.html +11 -1
- data/doc/TaskJuggler/TaskDependency.html +11 -1
- data/doc/TaskJuggler/TaskListAttribute.html +33 -23
- data/doc/TaskJuggler/TaskListRE.html +11 -1
- data/doc/TaskJuggler/TaskScenario.html +2007 -1919
- data/doc/TaskJuggler/TextFormatter.html +11 -1
- data/doc/TaskJuggler/TextParser.html +421 -612
- data/doc/TaskJuggler/TextParser/Pattern.html +410 -211
- data/doc/TaskJuggler/TextParser/Rule.html +224 -152
- data/doc/TaskJuggler/TextParser/StackElement.html +190 -28
- data/doc/TaskJuggler/TextParser/State.html +989 -0
- data/doc/TaskJuggler/TextParser/StateTransition.html +782 -0
- data/doc/TaskJuggler/TextParser/TextParserResultArray.html +25 -14
- data/doc/TaskJuggler/TextParser/TokenDoc.html +11 -1
- data/doc/TaskJuggler/TextReport.html +11 -1
- data/doc/TaskJuggler/TextScanner.html +285 -273
- data/doc/TaskJuggler/TextScanner/BufferStreamHandle.html +17 -7
- data/doc/TaskJuggler/TextScanner/FileStreamHandle.html +24 -14
- data/doc/TaskJuggler/TextScanner/MacroStackEntry.html +11 -1
- data/doc/TaskJuggler/TextScanner/StreamHandle.html +64 -52
- data/doc/TaskJuggler/TimeSheet.html +11 -1
- data/doc/TaskJuggler/TimeSheetReceiver.html +11 -1
- data/doc/TaskJuggler/TimeSheetRecord.html +11 -1
- data/doc/TaskJuggler/TimeSheetReport.html +11 -1
- data/doc/TaskJuggler/TimeSheetSender.html +11 -1
- data/doc/TaskJuggler/TimeSheetSummary.html +11 -1
- data/doc/TaskJuggler/TimeSheets.html +11 -1
- data/doc/TaskJuggler/Tj3AppBase.html +11 -1
- data/doc/TaskJuggler/Tj3Client.html +11 -1
- data/doc/TaskJuggler/Tj3Daemon.html +11 -1
- data/doc/TaskJuggler/Tj3SheetAppBase.html +11 -1
- data/doc/TaskJuggler/Tj3SsReceiver.html +11 -1
- data/doc/TaskJuggler/Tj3SsSender.html +11 -1
- data/doc/TaskJuggler/Tj3TsReceiver.html +11 -1
- data/doc/TaskJuggler/Tj3TsSender.html +11 -1
- data/doc/TaskJuggler/Tj3TsSummary.html +11 -1
- data/doc/TaskJuggler/TjException.html +11 -1
- data/doc/TaskJuggler/TjTime.html +474 -324
- data/doc/TaskJuggler/TjpExample.html +11 -1
- data/doc/TaskJuggler/TjpExportRE.html +11 -1
- data/doc/TaskJuggler/TjpSyntaxRules.html +3731 -3662
- data/doc/TaskJuggler/URLParameter.html +11 -1
- data/doc/TaskJuggler/UserManual.html +11 -1
- data/doc/TaskJuggler/VimSyntax.html +11 -1
- data/doc/TaskJuggler/WebServer.html +11 -1
- data/doc/TaskJuggler/WorkingHours.html +295 -221
- data/doc/TaskJuggler/WorkingHoursAttribute.html +11 -1
- data/doc/TaskJuggler/XMLBlob.html +11 -1
- data/doc/TaskJuggler/XMLComment.html +11 -1
- data/doc/TaskJuggler/XMLDocument.html +11 -1
- data/doc/TaskJuggler/XMLElement.html +11 -1
- data/doc/TaskJuggler/XMLNamedText.html +11 -1
- data/doc/TaskJuggler/XMLText.html +11 -1
- data/doc/index.html +694 -624
- data/doc/lib/AppConfig_rb.html +1 -1
- data/doc/lib/Attributes_rb.html +1 -1
- data/doc/lib/Booking_rb.html +1 -1
- data/doc/lib/DataCache_rb.html +69 -0
- data/doc/lib/KeywordDocumentation_rb.html +1 -1
- data/doc/lib/ProjectFileParser_rb.html +1 -1
- data/doc/lib/ProjectFileScanner_rb.html +1 -1
- data/doc/lib/Project_rb.html +1 -1
- data/doc/lib/PropertyList_rb.html +1 -1
- data/doc/lib/Query_rb.html +1 -1
- data/doc/lib/RealFormat_rb.html +1 -1
- data/doc/lib/ResourceScenario_rb.html +1 -1
- data/doc/lib/Resource_rb.html +1 -1
- data/doc/lib/RichTextParser_rb.html +1 -1
- data/doc/lib/RichTextScanner_rb.html +1 -1
- data/doc/lib/RichTextSyntaxRules_rb.html +1 -1
- data/doc/lib/RichText_rb.html +1 -1
- data/doc/lib/Scoreboard_rb.html +1 -1
- data/doc/lib/StatusSheetSender_rb.html +1 -1
- data/doc/lib/SyntaxReference_rb.html +1 -1
- data/doc/lib/TaskJuggler_rb.html +1 -1
- data/doc/lib/TaskScenario_rb.html +3 -1
- data/doc/lib/TextParser/Pattern_rb.html +3 -1
- data/doc/lib/TextParser/Rule_rb.html +3 -1
- data/doc/lib/TextParser/StackElement_rb.html +3 -1
- data/doc/lib/TextParser/State_rb.html +65 -0
- data/doc/lib/TextParser_rb.html +1 -1
- data/doc/lib/TextScanner_rb.html +1 -1
- data/doc/lib/Tj3Config_rb.html +1 -1
- data/doc/lib/TjTime_rb.html +1 -1
- data/doc/lib/TjpSyntaxRules_rb.html +1 -1
- data/doc/lib/WorkingHours_rb.html +3 -1
- data/doc/lib/daemon/ReportServer_rb.html +1 -1
- data/doc/lib/reports/CSVFile_rb.html +1 -1
- data/doc/lib/reports/CollisionDetector_rb.html +67 -0
- data/doc/lib/reports/GanttChart_rb.html +1 -1
- data/doc/lib/reports/GanttRouter_rb.html +3 -1
- data/doc/lib/reports/Navigator_rb.html +1 -1
- data/doc/lib/reports/ReportBase_rb.html +1 -1
- data/doc/lib/reports/ReportTableCell_rb.html +1 -1
- data/doc/lib/reports/ReportTableColumn_rb.html +1 -1
- data/doc/lib/reports/ReportTableLine_rb.html +1 -1
- data/doc/lib/reports/ReportTable_rb.html +1 -1
- data/doc/lib/reports/Report_rb.html +1 -1
- data/doc/lib/reports/TableReport_rb.html +1 -1
- data/doc/lib/taskjuggler3_rb.html +1 -1
- data/examples/tutorial.tjp +1 -2
- data/lib/AppConfig.rb +10 -4
- data/lib/Attributes.rb +4 -4
- data/lib/DataCache.rb +124 -0
- data/lib/KeywordDocumentation.rb +5 -5
- data/lib/Project.rb +54 -10
- data/lib/ProjectFileParser.rb +10 -9
- data/lib/ProjectFileScanner.rb +38 -25
- data/lib/PropertyList.rb +6 -4
- data/lib/Query.rb +0 -8
- data/lib/RealFormat.rb +1 -1
- data/lib/Resource.rb +1 -1
- data/lib/ResourceScenario.rb +96 -31
- data/lib/RichText.rb +17 -5
- data/lib/RichTextParser.rb +22 -9
- data/lib/RichTextScanner.rb +34 -34
- data/lib/RichTextSyntaxRules.rb +41 -36
- data/lib/Scoreboard.rb +16 -7
- data/lib/StatusSheetSender.rb +63 -0
- data/lib/SyntaxReference.rb +9 -10
- data/lib/TaskJuggler.rb +28 -4
- data/lib/TaskScenario.rb +66 -19
- data/lib/TextParser.rb +219 -384
- data/lib/TextParser/Pattern.rb +168 -49
- data/lib/TextParser/Rule.rb +33 -17
- data/lib/TextParser/StackElement.rb +42 -2
- data/lib/TextParser/State.rb +175 -0
- data/lib/TextScanner.rb +19 -15
- data/lib/Tj3Config.rb +1 -1
- data/lib/TjTime.rb +111 -3
- data/lib/TjpSyntaxRules.rb +122 -66
- data/lib/WorkingHours.rb +91 -186
- data/lib/daemon/ReportServer.rb +3 -2
- data/lib/reports/CSVFile.rb +1 -1
- data/lib/reports/CollisionDetector.rb +177 -0
- data/lib/reports/GanttChart.rb +25 -41
- data/lib/reports/GanttRouter.rb +104 -233
- data/lib/reports/Navigator.rb +1 -1
- data/lib/reports/Report.rb +9 -33
- data/lib/reports/ReportBase.rb +0 -1
- data/lib/reports/ReportTable.rb +19 -8
- data/lib/reports/ReportTableCell.rb +61 -25
- data/lib/reports/ReportTableColumn.rb +2 -2
- data/lib/reports/ReportTableLine.rb +4 -2
- data/lib/reports/TableReport.rb +1 -0
- data/lib/taskjuggler3.rb +0 -1
- data/manual/Installation +7 -3
- data/manual/Intro +12 -10
- data/manual/The_TaskJuggler_Syntax +4 -4
- data/test/TestSuite/CSV-Reports/celltext-Reference.csv +14 -14
- data/test/TestSuite/CSV-Reports/genrefs +1 -1
- data/test/TestSuite/CSV-Reports/resourcereport-Reference.csv +4 -4
- data/test/TestSuite/CSV-Reports/resourcereport_with_tasks-Reference.csv +22 -22
- data/test/TestSuite/CSV-Reports/sortByTree-Reference.csv +14 -14
- data/test/TestSuite/CSV-Reports/sortBy_duration.down-Reference.csv +14 -14
- data/test/TestSuite/CSV-Reports/sortBy_effort.up-Reference.csv +14 -14
- data/test/TestSuite/CSV-Reports/sortBy_plan.start.down-Reference.csv +14 -14
- data/test/TestSuite/CSV-Reports/taskreport-Reference.csv +14 -14
- data/test/TestSuite/CSV-Reports/taskreport_with_resources-Reference.csv +32 -24
- data/test/TestSuite/CSV-Reports/weekly-Reference.csv +13 -0
- data/test/TestSuite/CSV-Reports/weekly.tjp +9 -0
- data/test/TestSuite/HTML-Reports/css/tjreport.css +7 -2
- data/test/TestSuite/HTML-Reports/depArrows.html +839 -830
- data/test/TestSuite/HTML-Reports/depArrows.tjp +12 -12
- data/test/TestSuite/HTML-Reports/profile.html +37581 -0
- data/test/TestSuite/ReportGenerator/Errors/no_report_defined.tjp +7 -0
- data/test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.tjp +1 -1
- data/test/TestSuite/StatusSheets/TimeSheets/2002-03-01/missing-reports +2 -0
- data/test/TestSuite/StatusSheets/run +2 -0
- data/test/TestSuite/Syntax/Correct/Booking.tjp +13 -5
- data/test/TestSuite/Syntax/Correct/ResourceRoot.tjp +21 -0
- data/test/TestSuite/Syntax/Correct/RollupResource.tjp +21 -0
- data/test/TestSuite/Syntax/Correct/TaskRoot.tjp +1 -1
- data/test/TestSuite/Syntax/Errors/empty.tjp +1 -1
- data/test/TestSuite/Syntax/Errors/include_before_project.tjp +2 -0
- data/test/TestSuite/Syntax/Errors/no_reduce.tjp +6 -0
- data/test/TestSuite/Syntax/Errors/unsupported_token.tjp +1 -1
- data/test/TestSuite/TimeSheets/run +1 -1
- data/test/test_CSV-Reports.rb +2 -4
- data/test/test_CollisionDetector.rb +85 -0
- data/test/test_Project.rb +2 -2
- data/test/test_ProjectFileScanner.rb +73 -31
- data/test/test_Query.rb +2 -2
- data/test/test_ReportGenerator.rb +1 -1
- data/test/test_RichText.rb +4 -4
- data/test/test_WorkingHours.rb +150 -11
- metadata +75 -67
- data/test/TestSuite/ReportGenerator/Errors/css/tjreport.css +0 -407
- data/test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.html +0 -26
- data/test/TestSuite/Scheduler/Correct/Allocate.html +0 -3210
- data/test/TestSuite/Scheduler/Correct/Container.html +0 -349
- data/test/TestSuite/Scheduler/Correct/Limits.html +0 -4964
- data/test/TestSuite/Scheduler/Correct/Shift.html +0 -1719
- data/test/TestSuite/Scheduler/Correct/Shift2.html +0 -476
- data/test/TestSuite/Scheduler/Correct/css/tjmanual.css +0 -66
- data/test/TestSuite/Scheduler/Correct/icons/details.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/flag-green.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/flag-red.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/flag-yellow.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/resource.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/resourcegroup.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/task.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/taskgroup.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/trend-down.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/trend-flat.png +0 -0
- data/test/TestSuite/Scheduler/Correct/icons/trend-up.png +0 -0
- data/test/TestSuite/Scheduler/Correct/scripts/wz_tooltip.js +0 -1301
- data/test/TestSuite/Scheduler/Errors/css/tjmanual.css +0 -66
- data/test/TestSuite/Scheduler/Errors/css/tjreport.css +0 -407
- data/test/TestSuite/Scheduler/Errors/icons/details.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/flag-green.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/flag-red.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/flag-yellow.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/resource.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/resourcegroup.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/task.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/taskgroup.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/trend-down.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/trend-flat.png +0 -0
- data/test/TestSuite/Scheduler/Errors/icons/trend-up.png +0 -0
- data/test/TestSuite/Scheduler/Errors/scripts/wz_tooltip.js +0 -1301
- data/test/TestSuite/StatusSheets/resrep.tji +0 -7
- data/test/TestSuite/StatusSheets/tj3d.log +0 -312
- data/test/TestSuite/Syntax/Correct/Managers.html +0 -263
- data/test/TestSuite/TimeSheets/acceptable_intervals +0 -1
- data/test/TestSuite/TimeSheets/statussheets.log +0 -1
data/lib/SyntaxReference.rb
CHANGED
|
@@ -196,17 +196,16 @@ class TaskJuggler
|
|
|
196
196
|
# Push pattern onto 'stack'.
|
|
197
197
|
stack[pattern] = true
|
|
198
198
|
|
|
199
|
-
if pattern[0] == '
|
|
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 |
|
|
207
|
-
if
|
|
208
|
-
|
|
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
|
|
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] ==
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
-
#
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
1149
|
-
|
|
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
|
|
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
|
|
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
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
185
|
-
@stack.last.
|
|
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 |
|
|
284
|
-
type
|
|
285
|
-
|
|
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 #{
|
|
253
|
+
"The token #{name} is not supported here.")
|
|
290
254
|
end
|
|
291
|
-
elsif type ==
|
|
292
|
-
if @rules[
|
|
293
|
-
raise "Fatal Error: Reference to unknown rule #{
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
#
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
376
|
-
#puts "Finished rule #{rule.name}"
|
|
377
|
-
return result
|
|
351
|
+
@stack[0].val[0]
|
|
378
352
|
end
|
|
379
353
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
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 @
|
|
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
|
-
|