lib/TextParser.rb in taskjuggler-0.0.6 vs lib/TextParser.rb in taskjuggler-0.0.7

- old
+ new

@@ -11,10 +11,11 @@ # require 'TextParser/Pattern' require 'TextParser/Rule' require 'TextParser/StackElement' +require 'MessageHandler' require 'TjException' require 'Log' class TaskJuggler @@ -62,14 +63,17 @@ end end end - attr_reader :rules + attr_reader :rules, :messageHandler # Create a new TextParser object. - def initialize + def initialize(messageHandler) + # The message handler will collect all error messages. + @messageHandler = messageHandler + # This Hash will store the ruleset that the parser is operating on. @rules = { } # Array to hold the token types that the scanner can return. @variables = [] # An list of token types that are not allowed in the current context. @badVariables = [] @@ -153,30 +157,33 @@ end end # To parse the input this function needs to be called with the name of the # rule to start with. It returns the result of the processing function of - # the top-level parser rule that was specified by _ruleName_. + # the top-level parser rule that was specified by _ruleName_. In case of + # an error, the result is false. def parse(ruleName) @stack = [] @@expectedTokens = [] updateParserTables begin result = parseRuleR(@rules[ruleName]) - rescue TjException - # error('parse_error', $!.message) - return nil + rescue TjException => msg + if msg.message && !msg.message.empty? + @messageHandler.critical('parse', msg.message) + end + return false end result end # Return the SourceFileInfo of the TextScanner at the beginning of the # currently processed TextParser::Rule. Or return nil if we don't have a # current position. def sourceFileInfo - return nil if @stack.empty? + return nil if @stack.nil? || @stack.empty? @stack.last.sourceFileInfo[0] end def matchingRules(keyword) matches = [] @@ -185,22 +192,30 @@ matches << [ rule, patIdx ] if patIdx end matches end - def error(id, text, property = nil, sfi = nil) + def error(id, text, sfi = nil, data = nil) + sfi ||= sourceFileInfo if @scanner - @scanner.error(id, text, property, sfi) + # The scanner has some more context information, so we pass the error + # on to the TextScanner. + @scanner.error(id, text, sfi, data) else - message = Message.new(id, 'error', text, property, sfi) - @messageHandler.send(message) - raise TjException.new, '' + @messageHandler.error(id, text, sfi, data) end end - def warning(id, text, property = nil, sfi = nil) - @scanner.warning(id, text, property, sfi) + def warning(id, text, sfi = nil, data = nil) + sfi ||= sourceFileInfo + if @scanner + # The scanner has some more context information, so we pass the + # warning on to the TextScanner. + @scanner.warning(id, text, sfi, data) + else + @messageHandler.warning(id, text, sfi, data) + end end private # getTransitions recursively determines all possible target tokens @@ -232,11 +247,10 @@ allTokensOptional = false unless refRule.optional?(@rules) # Combine the hashes for each pattern into a single hash res.each do |pat_i| pat_i.each { |tok, r| transitions[tok] = r } end - optional = true if refRule.optional elsif '_$.'.include?(token[0]) transitions[token] = rule allTokensOptional = false else raise 'Fatal Error: Illegal token type specifier used for token' + @@ -247,10 +261,11 @@ # Make sure that we only have one possible transition for each # target. transitions.each do |key, value| rule.transitions.each do |trans| if trans.has_key?(key) + rule.dump raise "Fatal Error: Rule #{rule.name} has ambiguous " + "transitions for target #{key}" end end end @@ -269,12 +284,11 @@ type = tok[0] token = tok[1..-1] if type == ?$ if @variables.index(token).nil? error('unsupported_token', - "The token #{token} is not supported here.", nil, - token[2]) + "The token #{token} is not supported here.", token[2]) end elsif type == ?! if @rules[token].nil? raise "Fatal Error: Reference to unknown rule #{token} in " + "pattern '#{pat}' of rule #{rule.name}" @@ -289,10 +303,11 @@ # contains the reference to another rule. # This recursive version has cleaner code and is about 8% faster than # parseRuleNR. def parseRuleR(rule) #Log.enter('parseRuleR', "Parsing with rule #{rule.name}") + #puts "Parsing with rule #{rule.name}" result = rule.repeatable ? TextParserResultArray.new : nil # Rules can be marked 'repeatable'. This flag will be set to true after # the first iteration has been completed. repeatMode = false loop do @@ -319,10 +334,11 @@ else sfi = nil end @stack.last.store(parseRuleR(@rules[elToken]), sfi) #Log << "Resuming rule #{rule.name}" + #puts "Resuming rule #{rule.name}" else # In case the element is a keyword or variable we have to get a new # token if we don't have one anymore. token = getNextToken unless token @@ -355,10 +371,11 @@ result << res repeatMode = true end #Log.exit('parseRuleR', "Finished rule #{rule.name}") + #puts "Finished rule #{rule.name}" return result end # This function processes the input starting with the syntax description # of _rule_. It's implemented as an unrolled recursion. It recursively @@ -505,20 +522,16 @@ return result end def getNextToken - begin - token = nextToken - #Log << "Token: [#{token[0]}][#{token[1]}]" - rescue TjException - error('parse_rule', $!.message) - end + token = nextToken + #Log << "Token: [#{token[0]}][#{token[1]}]" if @badVariables.include?(token[0]) error('unsupported_token', "The token #{token[1]} is not supported in this context.", - nil, token[2]) + token[2]) end token end def findPattern(rule, token, repeatMode) @@ -559,11 +572,11 @@ "Unexpected token '#{token[1]}' of type " + "'#{token[0]}'. " : "Unexpected end of file in #{@scanner.fileName}. ") + (@@expectedTokens.length > 1 ? "Expecting one of #{@@expectedTokens.join(', ')}" : - "Expecting #{@@expectedTokens[0]}")) + "Expecting #{@@expectedTokens[0]}"), token[2]) end returnToken(token) return nil end @@ -579,28 +592,28 @@ text = "'#{elToken}' expected but found " + "'#{token[1]}' (#{token[0]})." unless @@expectedTokens.empty? text = "#{@@expectedTokens.join(', ')} or " + text end - error('spec_keywork_expctd', text) + error('spec_keywork_expctd', text, token[2]) end @stack.last.store(elToken, token[2]) elsif elType == ?. if token[0..1] != [ '.', '<END>' ] error('end_expected', - "Found garbage at expected end of file: #{token[1]}\n" + - "If you see this in the middle of your file, you probably " + - "have closed your context too early.") + "Found garbage at expected end of text: #{token[1]}\n" + + "If you see this in the middle of your text, you probably " + + "have closed your context too early.", token[2]) end else # The token must match the expected variable type. if token[0] != elToken text = "'#{elToken}' expected but found " + "'#{token[1]}' (#{token[0]})." unless @@expectedTokens.empty? text = "#{@@expectedTokens.join(', ')} or " + text end - error('spec_token_expctd', text) + error('spec_token_expctd', text, token[2]) end # If the element is a variable store the value of the token. @stack.last.store(token[1], token[2]) end end