# 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