File: Rule.rb

package info (click to toggle)
tj3 3.8.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,048 kB
  • sloc: ruby: 36,481; javascript: 1,113; sh: 19; makefile: 17
file content (206 lines) | stat: -rw-r--r-- 6,644 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = Rule.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
#               by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#

require 'taskjuggler/TextParser/State'

class TaskJuggler::TextParser

  # The TextParserRule holds the basic elment of the syntax description. Each
  # rule has a name and a set of patterns. The parser uses these rules to parse
  # the input files. The first token of a pattern must resolve to a terminal
  # token. The resolution can run transitively over a set of rules. The first
  # tokens of each pattern of a rule must resolve to a terminal symbol and all
  # terminals must be unique in the scope that they appear in. The parser uses
  # this first token to select the next pattern it uses for the syntactical
  # analysis. A rule can be marked as repeatable and/or optional. In this case
  # the syntax element described by the rule may occur 0 or multiple times in
  # the parsed file.
  class Rule

    attr_reader :name, :patterns, :optional, :repeatable, :keyword, :doc

    # Create a new syntax rule called +name+.
    def initialize(name)
      @name = name
      @patterns = []
      @repeatable = false
      @optional = false
      @keyword = nil

      flushCache
    end

    def flushCache
      # A rule is considered to describe optional tokens in case the @optional
      # flag is set or all of the patterns reference optional rules again.
      # This variable caches the transitively determined optional value.
      @transitiveOptional = nil
    end

    # Add a new +pattern+ to the Rule. It should be of type
    # TextParser::Pattern.
    def addPattern(pattern)
      @patterns << pattern
    end

    def include?(token)
      @patterns.each { |p| return true if p[0][1] == token }
      false
    end

    # Mark the rule as an optional element of the syntax.
    def setOptional
      @optional = true
    end

    # Return true if the rule describes optional elements. The evaluation
    # recursively descends into the pattern if necessary and stores the result
    # to be reused for later calls.
    def optional?(rules)
      # If we have a cached result, use this.
      return @transitiveOptional if @transitiveOptional

      # If the rule is marked optional, then it is optional.
      if @optional
        return @transitiveOptional = true
      end

      # If all patterns describe optional content, then this rule is optional
      # as well.
      @transitiveOptional = true
      @patterns.each do |pat|
        return @transitiveOptional = false unless pat.optional?(rules)
      end
    end

    def generateStates(rules)
      # First, add an entry State for this rule. Entry states are never
      # reached by normal state transitions. They are only used as (re-)start
      # states.
      states = [ State.new(self) ]

      @patterns.each do |pattern|
        states += pattern.generateStates(self, rules)
      end
      states
    end

    # Return a Hash of all state transitions caused by the 1st token of each
    # pattern of this rule.
    def addTransitionsToState(states, rules, stateStack, sourceState,
                              loopBack)
      @patterns.each do |pattern|
        pattern.addTransitionsToState(states, rules, stateStack.dup,
                                      sourceState, self, 0, loopBack)
      end
    end

    # Mark the syntax element described by this Rule as a repeatable element
    # that can occur once or more times in sequence.
    def setRepeatable
      @repeatable = true
    end

    # Add a description for the syntax elements of this Rule. +doc+ is a
    # RichText and +keyword+ is a unique name of this Rule. To avoid
    # ambiguouties, an optional scope can be appended, separated by a dot
    # (E.g. name.scope).
    def setDoc(keyword, doc)
      raise 'No pattern defined yet' if @patterns.empty?
      @patterns[-1].setDoc(keyword, doc)
    end

    # Add a description for a pattern element of the last added pattern.
    def setArg(idx, doc)
      raise 'No pattern defined yet' if @patterns.empty?
      @patterns[-1].setArg(idx, doc)
    end

    # Specify the index +idx+ of the last token to be used for the syntax
    # documentation. All subsequent tokens will be ignored.
    def setLastSyntaxToken(idx)
      raise 'No pattern defined yet' if @patterns.empty?
      raise 'Token index too large' if idx >= @patterns[-1].tokens.length
      @patterns[-1].setLastSyntaxToken(idx)
    end

    # Specify the support level of the current pattern.
    def setSupportLevel(level)
      raise 'No pattern defined yet' if @patterns.empty?
      @patterns[-1].setSupportLevel(level)
    end

    # Add a reference to another rule for documentation purposes.
    def setSeeAlso(also)
      raise 'No pattern defined yet' if @patterns.empty?
      @patterns[-1].setSeeAlso(also)
    end

    # Add a reference to a code example. +file+ is the name of the file. +tag+
    # is a tag within the file that specifies a part of this file.
    def setExample(file, tag)
      @patterns[-1].setExample(file, tag)
    end

    # Return a reference the pattern of this Rule.
    def pattern(idx)
      @patterns[idx]
    end

    def to_syntax(stack, docs, rules, skip)
      str = ''
      str << '[' if @optional || @repeatable
      str << '(' if @patterns.length > 1
      first = true
      pStr = ''
      @patterns.each do |pat|
        if first
          first = false
        else
          pStr << ' | '
        end
        pStr << pat.to_syntax_r(stack, docs, rules, skip)
      end
      return '' if pStr == ''
      str << pStr
      str << '...' if @repeatable
      str << ')' if @patterns.length > 1
      str << ']' if @optional || @repeatable
      str
    end

    def dump
      puts "Rule: #{name} #{@optional ? "[optional]" : ""} " +
           "#{@repeatable ? "[repeatable]" : ""}"
      @patterns.length.times do |i|
        puts "  Pattern: \"#{@patterns[i]}\""
        unless @transitions[i]
          puts "No transitions for this pattern!"
          next
        end

        @transitions[i].each do |key, rule|
          if key[0] == ?_
            token = "\"" + key.slice(1, key.length - 1) + "\""
          else
            token = key.slice(1, key.length - 1)
          end
          puts "    #{token} -> #{rule.name}"
        end
      end
      puts
    end

  end

end