lib/stupidedi/parser/tokenization.rb
# frozen_string_literal: true
module Stupidedi
using Refinements
module Parser
module Tokenization
#########################################################################
# @group Element Constructors
# Generates a repeated element (simple or composite)
#
# @return [void]
def repeated(*elements)
[:repeated, elements, Reader::Position.caller(2)]
end
# Generates a composite element
#
# @return [void]
def composite(*components)
[:composite, components, Reader::Position.caller(2)]
end
#########################################################################
# @group Element Placeholders
# Generates a blank element
#
# @return [void]
def blank
[:blank, nil, Reader::Position.caller(2)]
end
# Generates a blank element and asserts that the element's usage
# requirement is `NOT USED`
#
# @see Schema::ElementReq#forbidden?
#
# @return [void]
def not_used
[:not_used, nil, Reader::Position.caller(2)]
end
# Generates the only possible value an element may have, which may
# be blank. An exception is thrown if the element's usage requirement
# is optional, or if there are more than one allowed non-blank values.
#
# @see Schema::SimpleElementUse#allowed_values
# @see Schema::ElementReq#forbidden?
#
# @return [void]
def default
[:default, nil, Reader::Position.caller(2)]
end
# @endgroup
#########################################################################
private
# @return [Reader::SegmentTok]
def mksegment_tok(segment_dict, id, elements, position)
id = id.to_sym
element_toks = []
unless segment_dict.defined_at?(id)
element_idx = "00"
elements.each do |e_tag, e_val, e_position|
element_idx = element_idx.succ
# If the element is a regular Ruby value "ABC", the way this
# proc's arguments are assigned would be e_tag = "ABC", and
# e_val and e_position are nil. If the argument is a placeholder
# like #blank, #not_used, or #default is used, those methods return
# a triple that is deconstructed such that e_val is also nil.
#
# However, for #composite or #repeated, the triple is deconstructed
# such that e_val is an Array of other values, and isn't #nil?
unless e_val.nil?
raise ArgumentError,
"#{id}#{element_idx} is assumed to be a simple element"
end
element_toks << mksimple_tok(e_tag, e_position || position)
end
else
segment_def = segment_dict.at(id)
element_uses = segment_def.element_uses
if elements.length > element_uses.length
raise ArgumentError,
"#{id} segment has only #{element_uses.length} elements"
end
element_idx = "00"
elements.zip(element_uses) do |(e_tag, e_val, e_position), e_use|
element_idx = element_idx.succ
designator = "#{id}#{element_idx}"
if e_use.repeatable?
# Repeatable composite or non-composite
unless [:repeated, :blank, :not_used, nil].include?(e_tag)
raise ArgumentError,
"#{designator} is a repeatable element"
end
element_toks << mkrepeated_tok(e_val || [], e_use, designator, e_position || position)
elsif e_use.composite?
unless [:composite, :not_used, :blank, nil].include?(e_tag)
raise ArgumentError,
"#{id}#{element_idx} is a non-repeatable composite element"
end
element_toks << mkcomposite_tok(e_val || [], e_use, designator, e_position || position)
else
# The actual value is in e_tag
unless e_val.nil?
raise ArgumentError,
"#{id}#{element_idx} is a non-repeatable simple element"
end
element_toks << mksimple_tok(e_tag, e_position || position)
end
end
end
Reader::SegmentTok.new(id, element_toks, position, nil)
end
# @return [Reader::RepeatedElementTok]
def mkrepeated_tok(elements, element_use, designator, position)
element_toks = []
if element_use.composite?
elements.each do |e_tag, e_val, e_position|
unless [:composite, :not_used, :blank, nil].include?(e_tag)
raise ArgumentError,
"#{designator} is a composite element"
end
element_toks << mkcomposite_tok(e_val || [], element_use, designator, e_position || position)
end
else
elements.each do |e_tag, e_val, e_position|
unless e_val.nil?
raise ArgumentError,
"#{designator} is a simple element"
end
element_toks << mksimple_tok(e_tag, e_position || position)
end
end
Reader::RepeatedElementTok.build(element_toks, position)
end
# @return [Reader::CompositeElementTok]
def mkcomposite_tok(components, composite_use, designator, position)
component_uses = composite_use.definition.component_uses
if components.length > component_uses.length
raise ArgumentError,
"#{designator} has only #{component_uses.length} components"
end
component_idx = "0"
component_toks = []
component_uses.zip(components) do |c_use, (c_tag, c_val, c_position)|
component_idx = component_idx.succ
unless c_val.nil?
raise ArgumentError,
"#{designator}-#{component_idx} is a component element"
end
component_toks << mkcomponent_tok(c_tag, c_position || position)
end
Reader::CompositeElementTok.build(component_toks, position, nil)
end
# @return [Reader::ComponentElementTok]
def mkcomponent_tok(value, position)
Reader::ComponentElementTok.build(value, position, nil)
end
# @return [Reader::SimpleElementTok]
def mksimple_tok(value, position)
Reader::SimpleElementTok.build(value, position, nil)
end
# @endgroup
#########################################################################
end
end
end