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
|
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = State.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.
#
class TaskJuggler::TextParser
# A StateTransition maps a token type to the next state to be
# processed. A token descriptor is either a Symbol that maps to a RegExp in
# the TextScanner or an expected String. The transition may also have a
# list of State objects that are being activated by the transition.
class StateTransition
attr_reader :tokenType, :state, :stateStack, :loopBack
# Create a new StateTransition object. _descriptor_ is a [ token type,
# token value ] touple. _state_ is the State objects this transition
# originates at. _stateStack_ is the list of State objects that have been
# activated by this transition. _loopBack_ is a boolean flag that
# specifies whether the transition describes a loop back to the start of
# the Rule or not.
def initialize(descriptor, state, stateStack, loopBack)
if !descriptor.respond_to?(:length) || descriptor.length != 2
raise "Bad parameter descriptor: #{descriptor} " +
"of type #{descriptor.class}"
end
@tokenType = descriptor[0] == :eof ? :eof : descriptor[1]
if !state.is_a?(State)
raise "Bad parameter state: #{state} of type #{state.class}"
end
@state = state
if !stateStack.is_a?(Array)
raise "Bad parameter stateStack: #{stateStack} " +
"of type #{stateStack.class}"
end
@stateStack = stateStack.dup
@loopBack = loopBack
end
# Generate a human readable form of the TransitionState date. It's only
# used for debugging.
def to_s
str = "#{@state.rule.name}, " +
"#{@state.rule.patterns.index(@state.pattern)}, #{@state.index} "
unless @stateStack.empty?
str += "("
@stateStack.each do |s|
str += "#{s.rule.name} "
end
str += ")"
end
str += '(loop)' if @loopBack
str
end
end
# This State objects describes a state of the TextParser FSM. A State
# captures the position in the syntax description that the parser is
# currently at. A position is defined by the Rule, the Pattern and the index
# of the current token of that Pattern. An index of 0 means, we've just read
# the 1st token of the pattern. States which have no Pattern describe the
# start of rule. The parser has not yet identified the first token, so it
# doesn't know the Pattern yet.
#
# The actual data of a State is the list of possible StateTransitions to
# other states and a boolean flag that specifies if Reduce operations are
# valid for this State or not. The transitions are hashed by the token that
# would trigger this transition.
class State
attr_reader :rule, :pattern, :index, :transitions
attr_accessor :noReduce
def initialize(rule, pattern = nil, index = 0)
@rule = rule
@pattern = pattern
@index = index
# Starting states are always reduceable. Other states may or may not be
# reduceable. For now, we assume they are not.
@noReduce = !pattern.nil?
@transitions = {}
end
# Complete the StateTransition list. We can only call this function after
# all State objects for the syntax have been created. So we can't make
# this part of the constructor.
def addTransitions(states, rules)
if @pattern
# This is an normal state node.
@pattern.addTransitionsToState(states, rules, [], self,
@rule, @index + 1, false)
else
# This is a start node.
@rule.addTransitionsToState(states, rules, [], self, false)
end
end
# This method adds the actual StateTransition to this State.
def addTransition(token, nextState, stateStack, loopBack)
tr = StateTransition.new(token, nextState, stateStack, loopBack)
if @transitions.include?(tr.tokenType)
raise "Ambiguous transition for #{tr.tokenType} in \n#{self}\n" +
"The following transition both match:\n" +
" #{tr}\n #{@transitions[tr.tokenType]}"
end
@transitions[tr.tokenType] = tr
end
# Find the transition that matches _token_.
def transition(token)
if token[0] == :ID
# The scanner cannot differentiate between IDs and literals that look
# like IDs. So we look for literals first and then for IDs.
@transitions[token[1]] || @transitions[:ID]
elsif token[0] == :LITERAL
@transitions[token[1]]
else
@transitions[token[0]]
end
end
# Return a comma separated list of token strings that would trigger
# transitions for this State.
def expectedTokens
tokens = []
@transitions.each_key do |t|
tokens << "#{t.is_a?(String) ? "'#{t}'" : ":#{t}"}"
end
tokens
end
# Convert the State data into a human readable form. Used for debugging
# only.
def to_s(short = false)
if short
if @pattern
str = "#{rule.name} " +
"#{rule.patterns.index(@pattern)} #{@index}"
else
str = "#{rule.name} (Starting Node)"
end
else
if @pattern
str = "=== State: #{rule.name} " +
"#{rule.patterns.index(@pattern)} #{@index}" +
" #{@noReduce ? '' : '(R)'}" +
" #{'=' * 40}\nPattern: #{@pattern}\n"
else
str = "=== State: #{rule.name} (Starting Node) #{'=' * 30}\n"
end
@transitions.each do |type, target|
targetStr = target ? target.to_s : "<EOF>"
str += " #{type.is_a?(String) ? "'#{type}'" : ":#{type}"}" +
" => #{targetStr}\n"
end
str += "#{'=' * 76}\n"
end
str
end
end
end
|