# frozen-string-literal: true # # Copyright (C) 2019 Thomas Baron # # This file is part of term_utils. # # term_utils is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # term_utils is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with term_utils. If not, see . module TermUtils module AP # Represents the argument list parser. class Parser def initialize end # Parses a given command. # @param syntax [TermUtils::AP::Syntax] # @param command [String] # @param opts [Hash] `:program`. # @option opts [Boolean] :program Whether the first argument is the program name. # @return [TermUtils::AP::Result] # @raise [TermUtils::AP::ParseError] # @raise [TermUtils::AP::SyntaxError] def parse_command(syntax, command, opts = {}) parse_arguments(syntax, command.split(" "), opts) end # Parses a given list of arguments. # @param syntax [TermUtils::AP::Syntax] # @param arguments [Array] # @param opts [Hash] `:program`. # @option opts [Boolean] :program Whether the first argument is the program name. # @return [TermUtils::AP::Result] # @raise [TermUtils::AP::ParseError] # @raise [TermUtils::AP::SyntaxError] def parse_arguments(syntax, arguments, opts = {}) syntax = syntax.dup syntax.finalize! arguments = arguments.dup res = TermUtils::AP::Result.new(syntax) catch :done do parse0(res.value, syntax, arguments, opts) end res end private # Parses a given argument list. # @param node [TermUtils::PropertyTreeNode] # @param syntax [TermUtils::AP::Syntax] # @param arguments [Array] def parse0(node, syntax, arguments, opts = {}) # puts "parse0" opts = opts.dup if opts[:program] arguments.shift opts.delete :program end throw :done if arguments.empty? flagged_elems, unflagged_params = syntax.fetch_elements fp_occ = {} syntax.elements.each { |e| fp_occ[e.id] = 0 if e.flagged? } up_occ = 0 loop do break if arguments.empty? # puts "# %s" % arguments.first if arguments.first === "---" arguments.shift break end if flagged_elems.has_key? arguments.first # Flagged parameter/level. elem = flagged_elems[arguments.first] raise TermUtils::AP::ParseError, "parameter reached its occur limit" if elem.occur_bounded? && (fp_occ[elem.id] >= elem.max_occurs) fp_occ[elem.id] += 1 if elem.is_a? TermUtils::AP::Parameter arguments.shift sub_node = node.child_node(elem.id) sub_node = node.define_node(:key => elem.id) unless sub_node if elem.multiple_occurs? sub_node.child_nodes = [] if sub_node.leaf? idx = sub_node.child_nodes.length sub_node = sub_node.define_node(:key => idx) end catch :param_done do parse0_param(sub_node, elem, arguments, opts) end elsif elem.is_a? TermUtils::AP::Level arguments.shift sub_node = node.child_node(elem.id) sub_node = node.define_node(:key => elem.id) unless sub_node if elem.multiple_occurs? sub_node.child_nodes = [] if sub_node.leaf? idx = sub_node.child_nodes.length sub_node = sub_node.define_node(:key => idx) end parse0(sub_node, elem.syntax, arguments, opts) else raise TermUtils::AP::ParseError, "internal error - wrong kind of syntax element" end else # Unflagged parameter. raise TermUtils::AP::ParseError, "unexpected unflagged parameter" if unflagged_params.empty? raise TermUtils::AP::ParseError, "unexpected flagged parameter" if arguments.first.start_with? "-" elem = unflagged_params.shift sub_node = node.child_node(elem.id) sub_node = node.define_node(:key => elem.id) unless sub_node if elem.multiple_occurs? sub_node.child_nodes = [] if sub_node.leaf? idx = sub_node.child_nodes.length sub_node = sub_node.define_node(:key => idx) end catch :param_done do occ = up_occ up_occ = 0 parse0_param(sub_node, elem, arguments, opts) up_occ = occ up_occ += 1 if elem.multiple_occurs? && (!elem.occur_bounded? || (up_occ < elem.max_occurs)) unflagged_params.unshift elem else up_occ = 0 end end end end # loop end def parse0_param(node, param, arguments, opts = {}) arts = param.fetch_articles occ = 0 loop do if arguments.empty? raise TermUtils::AP::ParseError, "unexpected end of parameter" if !arts.empty? && (occ < arts.first.min_occurs) raise TermUtils::AP::ParseError, "unexpected end of parameter" if !arts.empty? && (self.class.eval_article_min_occurs(arts[1..-1]) > 0) throw :done end break if arts.empty? if arguments.first == "--" # End of parameter. raise TermUtils::AP::ParseError, "unexpected end of parameter" if !arts.empty? && (occ < arts.first.min_occurs) raise TermUtils::AP::ParseError, "unexpected end of parameter" if !arts.empty? && (self.class.eval_article_min_occurs(arts[1..-1]) > 0) arguments.shift throw :param_done elsif arguments.first == "-" # End of article. raise TermUtils::AP::ParseError, "unexpected article escape" if arts.empty? raise TermUtils::AP::ParseError, "unexpected end of parameter" if !arts.empty? && (occ < arts.first.min_occurs) arguments.shift arts.shift occ = 0 next end art = arts.first sub_node = node.child_node(art.id) sub_node = node.define_node(:key => art.id) unless sub_node if art.multiple_occurs? sub_node.child_nodes = [] if sub_node.leaf? idx = sub_node.child_nodes.length sub_node = sub_node.define_node(:key => idx) end sub_node.value = arguments.shift occ += 1 if !art.multiple_occurs? || (art.occur_bounded? && (occ >= art.max_occurs)) arts.shift occ = 0 end end # loop end # Evaluates the added number of min occurs of a given array of articles. # @param articles [Array] # @return [Integer] def self.eval_article_min_occurs(articles) occs = 0 articles.each { |a| occs += a.min_occurs } occs end end end end