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
|