require ‘shared_token_grammar’ require ‘open_ehr/assumed_library_types’ require ‘open_ehr/am/archetype/constraint_model’ require ‘open_ehr/am/openehr_profile/data_types/text’ require ‘cadl_node’

module OpenEHR

module Parser
  grammar CADL

# include OpenEHR::Parser::CADL

rule V_CADL_TEXT
  c_complex_object '' {
    def value
      c_complex_object.value
    end
  }
/ assertions '' {
    def value
      assertions.value
    end
  }
end

rule c_complex_object
  head:c_complex_object_head SYM_MATCHES SYM_START_CBLOCK body:c_complex_object_body SYM_END_CBLOCK space {
    def value(node = ArchetypeNode.new)
      args = head.value
      args[:occurrences] ||= OpenEHR::AssumedLibraryTypes::Interval.new(
        :lower => 1, :upper => 1)
      node.id = args[:node_id]
      if node.root? or node.id.nil?
        args[:path] = node.path
      else
        args[:path] = node.path + '[' + node.id + ']'
      end
      args.update body.value(node)
      OpenEHR::AM::Archetype::ConstraintModel::CComplexObject.new(args)
    end
  }
/ c_complex_object_head '' {
    def value(node = ArchetypeNode.new)
      args = c_complex_object_head.value
      args[:occurrences] ||= OpenEHR::AssumedLibraryTypes::Interval.new(
        :lower => 1, :upper => 1)
      node.id = args[:node_id]
      args[:path] = node.path
      OpenEHR::AM::Archetype::ConstraintModel::CComplexObject.new(args)
    end
  }
end

rule c_complex_object_head
  c_complex_object_id c_occurrences {
    def value
      args = c_complex_object_id.value
      args[:occurrences] = c_occurrences.value
      args
    end
  }
/ c_complex_object_id '' {
    def value
      c_complex_object_id.value
    end
  }
end

rule c_complex_object_id
  ti:type_identifier lo:V_LOCAL_TERM_CODE_REF space {
    def value
      {:rm_type_name => ti.value,
       :node_id => lo.value}
    end
  }
/ ti:type_identifier space {
    def value
      {:rm_type_name => ti.value}
    end
  }
end

rule c_complex_object_body
  c_any '' {
    def value(node)
      Hash[:attributes => [c_any.value(node)]]
    end
  }
/ c_attributes '' {
    def value(node)
      Hash[:attributes => c_attributes.value(node)]
    end
  }
end

rule c_object
  c_dv_quantity '' {
    def value(node)
      c_dv_quantity.value(node)
    end
  }          
/ c_ordinal '' {
    def value(node)
      args = c_ordinal.value
      args[:path] = node.path
      args[:rm_type_name] = 'DvOrdinal'
      args[:occurrences] ||= OpenEHR::AssumedLibraryTypes::Interval.new(
        :upper => 1, :lower => 1)
      OpenEHR::AM::OpenEHRProfile::DataTypes::Quantity::CDvOrdinal.new(
       args)
    end
  }
/ c_primitive_object '' {
    def value(node)
      c_primitive_object.value
    end
  }
/ c_complex_object '' {
    def value(node)
      c_complex_object.value(node)
    end
  }
/ c_code_phrase '' {
    def value(node)
      c_code_phrase.value(node)
    end
  }
/ constraint_ref '' {
    def value(node)
      constraint_ref.value(node)
    end
  }
/ archetype_slot '' {
    def value(node)
      archetype_slot.value(node)
    end
  }
/ archetype_internal_ref '' {
    def value(node = nil)
      archetype_internal_ref.value(node)
    end
  }
/ V_C_DOMAIN_TYPE '' {
    def value(node = nil)
      p elemetns
    end
  }

# / ERR_V_C_DOMAIN_TYPE

 end

 rule archetype_internal_ref
   SYM_USE_NODE type_identifier c_occurrences object_path space {
     def value(node)
       OpenEHR::AM::Archetype::ConstraintModel::ArchetypeInternalRef.new(
         :rm_type_name => type_identifier.value,
         :occurrences => c_occurrences.value,
         :path => node.path,
         :target_path => object_path.value)
     end
   }
 / SYM_USE_NODE type_identifier object_path space {
     def value(node = nil)
       OpenEHR::AM::Archetype::ConstraintModel::ArchetypeInternalRef.new(
         :rm_type_name => type_identifier.value,
         :occurrences => OpenEHR::AssumedLibraryTypes::Interval.new(
           :lower => 1, :upper => 1),
         :path => node.path,
         :target_path => object_path.value)
      end
   }
 end

 rule archetype_slot
   c_archetype_slot_head SYM_MATCHES SYM_START_CBLOCK c_includes c_excludes SYM_END_CBLOCK space {
     def value(node)
       args = c_archetype_slot_head.value(node)
       args[:includes] = c_includes.value
       args[:excludes] = c_excludes.value
       OpenEHR::AM::Archetype::ConstraintModel::ArchetypeSlot.new(args)
     end
   }
 / c_archetype_slot_head SYM_MATCHES SYM_START_CBLOCK c_includes SYM_END_CBLOCK space {
     def value(node)
       args = c_archetype_slot_head.value(node)
       args[:includes] = c_includes.value
       OpenEHR::AM::Archetype::ConstraintModel::ArchetypeSlot.new(args)
     end
   }
 / c_archetype_slot_head SYM_MATCHES SYM_START_CBLOCK c_excludes SYM_END_CBLOCK space {
     def value(node)
       args = c_archetype_slot_head.value(node)
       args[:excludes] = c_excludes.value
       OpenEHR::AM::Archetype::ConstraintModel::ArchetypeSlot.new(args)
     end
   }
 end

 rule c_archetype_slot_head
   c_archetype_slot_id white_space c_occurrences {
     def value(node)
       args = c_archetype_slot_id.value(node)
       args[:occurrences] = c_occurrences.value
       args
     end
   }
 / c_archetype_slot_id white_space {
     def value(node)
       args = c_archetype_slot_id.value(node)
       args[:occurrences] = OpenEHR::AssumedLibraryTypes::Interval.new(
         :upper => 1, :lower => 1)
       args
     end
   }
 end

 rule c_archetype_slot_id
   SYM_ALLOW_ARCHETYPE type_identifier lt:V_LOCAL_TERM_CODE_REF {
     def value(node)
       {:rm_type_name => type_identifier.value,
        :node_id => lt.value,
        :path => node.path + "[#{lt.value}]"}
     end
   }
 / SYM_ALLOW_ARCHETYPE type_identifier {
     def value(node)
       {:rm_type_name => type_identifier.value,
        :path => node.path}
     end
   }
 end

 rule c_primitive_object
   c_primitive '' {
     def value
       c_primitive.value
     end
   }
 end

 rule c_primitive
   c_boolean '' {
     def value
       c_boolean.value
     end
   }
 / c_date_time '' {
     def value
       c_date_time.value
     end
   }
 / c_time '' {
     def value
       c_time.value
     end
   }
 / c_date '' {
     def value
       c_date.value
     end
   }
 / c_duration '' {
     def value
       c_duration.value
     end
   }
 / c_real '' {
     def value
       c_real.value
     end
   }
 / c_integer '' {
     def value
       c_integer.value
     end
   }       
 / c_string '' {
     def value
       c_string.value
     end
   }
 end

 rule c_any
   '*' space {
     def value(node)
       OpenEHR::AM::Archetype::ConstraintModel::CAttribute.new(
         :path => node.path, :rm_attribute_name => 'ANY',
         :exsitence => OpenEHR::AssumedLibraryTypes::Interval.new(
           :lower => 1, :upper => 1))
     end
   }
 end

 rule c_attributes
   c_attribute more_attr:(c_attribute white_space)* {
     def value(node)
       attributes.map {|c| c.value(node)}
     end

     def attributes
       [c_attribute] + more_attr.elements.map {|e| e.c_attribute}
     end
   }
 end

 rule c_attribute
   c_attr_head c_attr_body {
     def value(node)
       val = c_attr_head.value(node)
       child_node = ArchetypeNode.new(node)
       child_node.path = val.path
       val.children = c_attr_body.value(child_node)
       val
     end
   }
 end

 rule c_attr_head
   id:(V_ATTRIBUTE_IDENTIFIER) white_space c_existence c_cardinality {
     def value(node)
       if node.root?
         path = node.path + id.value
       elsif node.id
         path = node.path + "[#{node.id}]/" + id.value
       elsif
         path = node.path + '/' + id.value
       end
       OpenEHR::AM::Archetype::ConstraintModel::CMultipleAttribute.new(
        :rm_attribute_name => id.value,
        :path => path,
        :existence => c_existence.value,
        :cardinality => c_cardinality.value)
     end
   }
 / id:V_ATTRIBUTE_IDENTIFIER white_space c_existence {
     def value(node)
       if node.root?
         path = node.path + id.value
       elsif node.id
         path = node.path + "[#{node.id}]/" + id.value
       elsif
         path = node.path + '/' + id.value
       end
       OpenEHR::AM::Archetype::ConstraintModel::CSingleAttribute.new(
        :rm_attribute_name => id.value,
        :path => path,
        :existence => c_existence.value)
     end
   }
 / id:(V_ATTRIBUTE_IDENTIFIER) white_space c_cardinality {
     def value(node)
       if node.root?
         path = node.path + id.value
       elsif node.id
         path = node.path + "[#{node.id}]/" + id.value
       elsif
         path = node.path + '/' + id.value
       end
       OpenEHR::AM::Archetype::ConstraintModel::CMultipleAttribute.new(
        :rm_attribute_name => id.value,
        :path => path,
        :cardinality => c_cardinality.value)
     end
   }
 / id:(V_ATTRIBUTE_IDENTIFIER) white_space {
     def value(node)
       if node.root?
         path = node.path + id.value
       elsif node.id
         path = node.path + "[#{node.id}]/" + id.value
       elsif
         path = node.path + '/' + id.value
       end
       OpenEHR::AM::Archetype::ConstraintModel::CSingleAttribute.new(
        :rm_attribute_name => id.value, :path => path)
     end
   }
 end

 rule c_attr_body
   SYM_MATCHES SYM_START_CBLOCK c_attr_values SYM_END_CBLOCK space {
     def value(node)
       c_attr_values.value(node)
     end
   }
 end

 rule c_attr_values
   c_any '' {
     def value(node)
       [c_any.value(node)]
     end
   }
 / c_object more_co:(c_object '')* {
     def value(node)
       c_objects.map {|c| c.value(node)}
     end

     def c_objects
       [c_object] + more_co.elements.map {|e| e.c_object }
     end
   }
 end

 rule c_includes
   SYM_INCLUDE assertions {
     def value
       assertions.value
     end
   }
 end

 rule c_excludes
   SYM_EXCLUDE assertions {
     def value
       assertions.value
     end
   }
 end

 rule c_existence
   SYM_EXISTENCE SYM_MATCHES SYM_START_CBLOCK existence_spec SYM_END_CBLOCK space {
     def value
       existence_spec.value
     end
   }
 end

 rule existence_spec
   lo:V_INTEGER SYM_ELLIPSIS up:V_INTEGER {
     def value
       OpenEHR::AssumedLibraryTypes::Interval.new(:lower => lo.value, :upper => up.value)
     end
   }
 / V_INTEGER '' {
     def value
       OpenEHR::AssumedLibraryTypes::Interval.new(:lower => V_INTEGER.value, :upper => V_INTEGER.value)
     end
   }
 end

 rule c_cardinality
   SYM_CARDINALITY SYM_MATCHES SYM_START_CBLOCK cardinality_spec SYM_END_CBLOCK space {
     def value
       cardinality_spec.value
     end
   }
 end

 rule cardinality_spec
   occurrence_spec ';' white_space SYM_ORDERED ';' white_space SYM_UNIQUE {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_unique => true,
         :is_orderd => true)
     end
   }
 / occurrence_spec ';' white_space SYM_ORDERED {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_orderd => true)
     end
   }
 / occurrence_spec ';' white_space SYM_UNORDERD ';' white_space SYM_UNIQUE {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_unique => true,
         :is_orderd => false)
     end
   }
 / occurrence_spec ';' white_space SYM_UNORDERD {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_orderd => false)
     end
   }
 / occurrence_spec SYM_UNIQUE ';' white_space SYM_ORDERED {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_unique => true,
         :is_orderd => true)
     end
   }
 / occurrence_spec SYM_UNIQUE ';' white_space SYM_UNORDERD {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_unique => true,
         :is_ordered => false)
     end
   }
 / occurrence_spec SYM_UNIQUE {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value,
         :is_unique => true)
     end
   }
 / occurrence_spec space {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Cardinality.new(
         :interval => occurrence_spec.value)
     end
   }
 end

 rule c_occurrences
   SYM_OCCURRENCES SYM_MATCHES SYM_START_CBLOCK occurrence_spec SYM_END_CBLOCK space {
     def value
       occurrence_spec.value
     end
   }
 end

 rule occurrence_spec
   st:integer_value SYM_ELLIPSIS ed:cardinality_limit_value {
     def value
       if ed.value == '*'
         OpenEHR::AssumedLibraryTypes::Interval.new(
            :lower => st.value)
       else
         OpenEHR::AssumedLibraryTypes::Interval.new(
           :lower => st.value,
           :upper => ed.value)
       end
     end
   }
 / cardinality_limit_value '' {
     def value
       OpenEHR::AssumedLibraryTypes::Interval.new(
         :lower => cardinality_limit_value.value,
         :upper => cardinality_limit_value.value)
     end
   }
 end

 rule cardinality_limit_value
   integer_value '' {
     def value
       text_value.to_i
     end
   }
 / '*' {
     def value
       '*'
     end
   }
 end

 rule c_integer
   c_integer_spec ';' white_space integer_value {
     def value
       args = c_integer_spec.value
       args[:assumed_value] = integer_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CInteger.new(args)
     end
   }
 / c_integer_spec '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CInteger.new(
         c_integer_spec.value)
     end
   }
 end

 rule c_integer_spec
   integer_list_value '' {
     def value
       {:list => integer_list_value.value}
     end
   }
 / integer_interval_value '' {
     def value
       {:range => integer_interval_value.value}
     end
   }
 / integer_value !'..' '' {
     def value
       {:list => [integer_value.value]}
     end
   }
 / occurrence_spec '' {
     def value
       {:range => occurrence_spec.value}
     end
   }
 end

 rule c_real_spec
   real_list_value '' {
     def value
       {:list => real_list_value.value}
     end
   }
 / real_interval_value '' {
     def value
       {:range => real_interval_value.value}
     end
   }
 / real_value '' {
     def value
       {:list => [real_value.value]}
     end
   }
 end

 rule c_real
   c_real_spec ';' white_space real_value {
     def value
       args = c_real_spec.value
       args[:assumed_value] = real_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CReal.new(args)
     end
   }
 / c_real_spec '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CReal.new(
         c_real_spec.value)
     end
   }
 end

 rule c_date_constraint
   date_interval_value '' {
     def value
       {:range => date_interval_value.value}
     end
   }
 / date_list_value '' {
     def value
       {:list => date_list_value.value}
     end
   }
 / date_value '' {
     def value
       {:list => [date_value.value]}
     end
   }
 / con:V_ISO8601_DATE_CONSTRAINT_PATTERN '' {
     def value
       {:pattern => con.text_value}
     end
   }
 end

 rule c_date
   c_date_constraint ';' white_space date_value {
     def value
       args = c_date_constraint.value
       args[:assumed_value] = date_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CDate.new(
         args)
     end
   }
 / c_date_constraint '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CDate.new(
         c_date_constraint.value)
     end
   }
 end

 rule c_time_constraint
   time_interval_value '' {
     def value
       {:range => time_interval_value.value}
     end
   }
 / time_list_value '' {
     def value
       {:list => time_list_value.value}
     end
   }
 / time_value '' {
     def value
       {:list => [time_value.value]}
     end
   }
 / tc:V_ISO8601_TIME_CONSTRAINT_PATTERN '' {
     def value
       {:pattern => tc.text_value}
     end
   }
 end

 rule c_time
   c_time_constraint ';' white_space time_value {
     def value
       args = c_time_constraint.value
       args[:assumed_value] = time_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CTime.new(
         args)
     end
   }    
 / c_time_constraint '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CTime.new(
         c_time_constraint.value)
     end
   }
 end

 rule c_date_time_constraint
   date_time_interval_value '' {
     def value
       {:range => date_time_interval_value.value}
     end
   }
 / date_time_value '' {
     def value
       {:list => [date_time_value.value]}
     end
   }
 / dtc:V_ISO8601_DATE_TIME_CONSTRAINT_PATTERN '' {
     def value
       {:pattern => dtc.text_value}
     end
   }
 end

 rule c_date_time
   c_date_time_constraint ';' white_space date_time_value {
     def value
       args = c_date_time_constraint.value
       args[:assumed_value] = date_time_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CDateTime.new(
         args)
     end
   }
 / c_date_time_constraint '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CDateTime.new(
         c_date_time_constraint.value)
     end
   }
 end

 rule c_duration_constraint
   duration_pattern '/' duration_interval_value '' {
     def value
       {:pattern => duration_pattern.value}
     end
   }
/ duration_pattern '' {
     def value
       {:pattern => duration_pattern.value}
     end
   }
 / duration_interval_value '' {
     def value
       {:range => duration_interval_value.value}
     end
   }
 / duration_value '' {
     def value
       {:list => [duration_value.value]}
     end
   }
 end

 rule duration_pattern
   dp:V_ISO8601_DURATION_CONSTRAINT_PATTERN '' {
     def value
       dp.text_value
     end
   }
 end

 rule c_duration
   c_duration_constraint ';' white_space duration_value '' {
     def value
       args = c_duration_constraint.value
       args[:assumed_value] = duration_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CDuration.new(
         args)
     end
   }
 / c_duration_constraint '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CDuration.new(
         c_duration_constraint.value)
     end
   }
 end

 rule c_string_spec
   string_list_value ',' white_space SYM_LIST_CONTINUE {
     def value
       {:list => string_list_value.value}
     end
   }
 / string_list_value '' {
     def value
       {:list => string_list_value.value}
     end
   }
 / pat:V_REGEXP '' {
     def value
       {:pattern => pat.value}
     end
   }
 / str:V_STRING '' {
     def value
       {:list => [str.value]}
     end
   }
 end

 rule c_string
   c_string_spec white_space ';' white_space string_value {
     def value
       args = c_string_spec.value
       args[:assumed_value] = string_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CString.new(
         args)
     end
   }
 / c_string_spec '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CString.new(
         c_string_spec.value)
     end
   }
 end

 rule c_boolean_spec
   SYM_TRUE ',' white_space SYM_FALSE {
     def value
       {:true_valid => true, :false_valid => true}
     end
   }
 / SYM_TRUE white_space '' {
     def value
       {:true_valid => true, :false_valid => false}
     end
   }
 / SYM_FALSE white_space ',' white_space SYM_TRUE {
     def value
       {:true_valid => true, :false_valid => true}
     end
   }
 / SYM_FALSE white_space '' {
     def value
       {:false_valid => true, :true_valid => false}
     end
   }
 end

 rule c_boolean
   c_boolean_spec ';' white_space boolean_value {
     def value
       args = c_boolean_spec.value
       args[:assumed_value] = boolean_value.value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CBoolean.new(
         args)
     end
   }
 / c_boolean_spec '' {
     def value
       OpenEHR::AM::Archetype::ConstraintModel::Primitive::CBoolean.new(
         c_boolean_spec.value)
     end
   }
 end

 rule constraint_ref
   vltcr:V_LOCAL_TERM_CODE_REF '' {
     def value(node)
       OpenEHR::AM::Archetype::ConstraintModel::ConstraintRef.new(
         :path => node.path,
         :rm_type_name => 'ConstraintRef',
         :occurrences => OpenEHR::AssumedLibraryTypes::Interval.new(:upper => 1, :lower => 1),
         :reference => vltcr.value)
     end
   }
 end

 rule V_REGEXP
   (('=' / '!') '~')? (('/' ('\/' / !'/' .)* '/') / ('^' (!'^' .)* '^') ) {
     def value
       text_value
     end
   }      
 end

 rule code_string
   NAMECHAR+ '' {
     def value
       text_value
     end
   }
 end

 rule code_string_list
   first:code_string more:(',' space code_string)+ space {
     def value
       codes.map {|c| c.value}
     end

     def codes
       [first] + more.elements.map {|e| e.code_string}
     end
   }
 end

 rule code_string_spec
   code_string_list '' {
     def value
       code_string_list.value
     end
   }
 / code_string '' {
     def value
       [code_string.value]
     end
     }
 end

 rule c_code_phrase
   ti:V_TERM_CODE code_string_spec ';' space code_string ']' space {
     def value(node)
       ::OpenEHR::AM::OpenEHRProfile::DataTypes::Text::CCodePhrase.new(
         :rm_type_name => 'CodePhrase',
         :occurrences => OpenEHR::AssumedLibraryTypes::Interval.new(
                           :upper => 1, :lower => 1),
         :terminology_id => ti.value, :code_list => code_string_spec.value,
         :assumed_value => code_string.value,
         :path => node.path)
     end
   }
 / ti:V_TERM_CODE code_string_spec ']' space {
     def value(node)
       ::OpenEHR::AM::OpenEHRProfile::DataTypes::Text::CCodePhrase.new(
         :rm_type_name => 'CodePhrase',
         :occurrences => OpenEHR::AssumedLibraryTypes::Interval.new(
                           :upper => 1, :lower => 1),
         :terminology_id => ti.value, :code_list => code_string_spec.value,
         :path => node.path)
     end
   }
 / ti:V_TERM_CODE ']' space {
     def value(node)
       ::OpenEHR::AM::OpenEHRProfile::DataTypes::Text::CCodePhrase.new(
         :rm_type_name => 'CodePhrase',
         :occurrences => OpenEHR::AssumedLibraryTypes::Interval.new(
                           :upper => 1, :lower => 1),
         :terminology_id => ti.value, :code_list => [], :path => node.path)
     end
   }
 end

 rule SYM_C_DV_ORDINAL
   'C_DV_ORDINAL' space 
 end

 rule c_ordinal
   c_ordinal_spec ';' space integer_value space {
     def value
       args = c_ordinal_spec.value
       args[:assumed_value] = integer_value.value
       args
     end
   }
 / c_ordinal_spec space '' {
     def value
       c_ordinal_spec.value
     end
   }
 / SYM_C_DV_ORDINAL SYM_LT white_space SYM_GT space {
     def value
       {:list => nil}
     end
 }
 end

 rule c_ordinal_spec
   ordinal_list '' {
     def value
       {:list => ordinal_list.value}
     end
   }
 / ordinal '' {
     def value
       {:list => [ordinal.value]}
     end
   }
 end

 rule ordinal_list
   first:ordinal more:(',' space ordinal)+ {
     def value
       ordinals.map {|o| o.value}
     end

     def ordinals
       [first] + more.elements.map {|e| e.ordinal}
     end
   }
 end

 rule ordinal
   integer_value SYM_INTERVAL_DELIM vqtc:V_QUALIFIED_TERM_CODE_REF !SYM_INTERVAL_DELIM '' {
     def value
       symbol = ::OpenEHR::RM::DataTypes::Text::DvCodedText.new(
         :value => vqtc.text_value, :defining_code => vqtc.value)
       ::OpenEHR::RM::DataTypes::Quantity::DvOrdinal.new(
           :value => integer_value.value,
           :symbol => symbol)
     end
   }
 end

 rule c_dv_quantity
   SYM_C_DV_QUANTITY SYM_START_DBLOCK
   prop:property? ql:quantity_list? aqv:assumed_quantity_value?
   SYM_END_DBLOCK <CDvQuantityItems>
 / SYM_C_DV_QUANTITY SYM_START_DBLOCK
   prop:property? aqv:assumed_quantity_value? ql:quantity_list?
   SYM_END_DBLOCK <CDvQuantityItems>
 / SYM_C_DV_QUANTITY SYM_START_DBLOCK
   aqv:assumed_quantity_value? prop:property? ql:quantity_list?
   SYM_END_DBLOCK <CDvQuantityItems>
 / SYM_C_DV_QUANTITY SYM_START_DBLOCK
   aqv:assumed_quantity_value? ql:quantity_list? prop:property?
   SYM_END_DBLOCK <CDvQuantityItems>
 / SYM_C_DV_QUANTITY SYM_START_DBLOCK
   ql:quantity_list? aqv:assumed_quantity_value? prop:property?
   SYM_END_DBLOCK <CDvQuantityItems>
 / SYM_C_DV_QUANTITY SYM_START_DBLOCK
   ql:quantity_list? prop:property? aqv:assumed_quantity_value?
   SYM_END_DBLOCK <CDvQuantityItems>
 end

 rule SYM_C_DV_QUANTITY
   'C_DV_QUANTITY' space
 end

 rule property
   SYM_PROPERTY SYM_EQ SYM_START_DBLOCK
     prop:V_QUALIFIED_TERM_CODE_REF SYM_END_DBLOCK {
     def value
       prop.value
     end
   }
 end

 rule SYM_PROPERTY
   'property' space
 end

 rule quantity_list
   SYM_QUANTITY_LIST SYM_EQ SYM_START_DBLOCK
   c_quantity_items SYM_END_DBLOCK {
     def value(node)
       c_quantity_items.value(node)
     end
   }
 end

 rule assumed_quantity_value
   SYM_ASSUMED_VALUE SYM_EQ SYM_START_DBLOCK 
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK val:real_value SYM_END_DBLOCK)?      
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK val:integer_value SYM_END_DBLOCK)?        
   SYM_END_DBLOCK <AssumedValueItems>
 / SYM_ASSUMED_VALUE SYM_EQ SYM_START_DBLOCK 
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK val:integer_value SYM_END_DBLOCK)?        
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK val:real_value SYM_END_DBLOCK)?      
   SYM_END_DBLOCK <AssumedValueItems>
 / SYM_ASSUMED_VALUE SYM_EQ SYM_START_DBLOCK 
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK val:integer_value SYM_END_DBLOCK)?        
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK val:real_value SYM_END_DBLOCK)?      
   SYM_END_DBLOCK <AssumedValueItems>
 / SYM_ASSUMED_VALUE SYM_EQ SYM_START_DBLOCK 
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK val:integer_value SYM_END_DBLOCK)?        
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK val:real_value SYM_END_DBLOCK)?      
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   SYM_END_DBLOCK <AssumedValueItems>
 / SYM_ASSUMED_VALUE SYM_EQ SYM_START_DBLOCK 
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK val:real_value SYM_END_DBLOCK)?      
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK val:integer_value SYM_END_DBLOCK)?        
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   SYM_END_DBLOCK <AssumedValueItems>
 / SYM_ASSUMED_VALUE SYM_EQ SYM_START_DBLOCK 
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK val:real_value SYM_END_DBLOCK)?      
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK val:integer_value SYM_END_DBLOCK)?        
   SYM_END_DBLOCK <AssumedValueItems>
 end

 rule SYM_QUANTITY_LIST
   'list' space
 end

 rule c_quantity_items
   c_quantity_item more_cdv:(c_quantity_item white_space)* {
     def value(node)
       quantities.map {|q| q.value(node)}
     end

     def quantities
       [c_quantity_item] + more_cdv.elements.map {|e| e.c_quantity_item}
     end
   }
 end

 rule c_quantity_item
   '[' V_STRING ']' white_space SYM_EQ SYM_START_DBLOCK
   SYM_UNITS SYM_EQ SYM_START_DBLOCK units:V_STRING SYM_END_DBLOCK
   mag:(SYM_MAGNITUDE SYM_EQ SYM_START_DBLOCK int:real_interval_value SYM_END_DBLOCK)?
   prec:(SYM_PRECISION SYM_EQ SYM_START_DBLOCK int:integer_interval_value SYM_END_DBLOCK)?
   SYM_END_DBLOCK {
     def value(node)
       magnitude, precision = nil
       magnitude = mag.int.value unless mag.empty?
       precision = prec.int.value unless prec.empty?
       OpenEHR::AM::OpenEHRProfile::DataTypes::Quantity::CQuantityItem.new(
         :path => node.path, :rm_type_name => 'DvQuantity',
         :occurrences => OpenEHR::AssumedLibraryTypes::Interval.new(
           :upper => 1, :lower => 1),
         :units => units.value,
         :magnitude => magnitude, :precision => precision)
     end
   }
 end

 rule SYM_UNITS
   'units' space
 end

 rule SYM_MAGNITUDE
   'magnitude' space
 end

 rule SYM_PRECISION
   'precision' space
 end

 rule SYM_ASSUMED_VALUE
   'assumed_value' space
 end

 rule V_C_DOMAIN_TYPE
   '('? [A-Z] IDCHAR* ')'? [ \n]* '<' [^>]* '>'
 end

# assertion block

rule V_ASSERTION_TEXT
  assertions '' {
    def value
      assertions.value
    end
  }
end

rule assertions
  assertion more_a:(assertion '')* {
    def value
      assertions.map {|a| a.value}
    end

    def assertions
      [assertion] + more_a.elements.map {|a| a.assertion}
    end
  }
end

rule assertion
  id:(any_identifier ':')? boolean_expression space {
    def value
      if (id && !id.empty?)
        OpenEHR::AM::Archetype::Assertion::Assertion.new(
          :tag => id.value, :expression => boolean_expression.value,
          :string_expression => id.text_value + boolean_expression.text_value)
      else
        OpenEHR::AM::Archetype::Assertion::Assertion.new(
          :expression => boolean_expression.value,
          :string_expression => boolean_expression.text_value)
      end
    end
  }
end

rule boolean_expression
  boolean_node '' {
    def value
      boolean_node.value
    end
  }
/ boolean_leaf '' {
    def value
      boolean_leaf.value
    end
  }
end

rule boolean_node
  SYM_EXISTS absolute_path {
    def value
      item = OpenEHR::AM::Archetype::Assertion::ExprLeaf.new(
        :type => 'String',
        :item => absolute_path.value,
        :reference_type => 'CONSTANT')
      OpenEHR::AM::Archetype::Assertion::ExprUnaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_EXISTS,
        :operand => item,
        :precedence_overridden => false)
    end
  }
/ relative_path white_space SYM_MATCHES SYM_START_CBLOCK c_primitive SYM_END_CBLOCK {
    def value
      left_op = OpenEHR::AM::Archetype::Assertion::ExprLeaf.new(
                  :type => 'String',
                  :item => relative_path.value,
                  :reference_type => 'Constant')
      right_op = OpenEHR::AM::Archetype::Assertion::ExprLeaf.new(
                  :item => c_primitive.value,
                  :type => c_primitive.value.type,
                  :reference_type => 'Constant')
      op = OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_MATCHES
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => op,
        :right_operand => right_op,
        :left_operand => left_op,
        :reference_type => 'Constraint')
    end
  }
/ SYM_NOT boolean_leaf '' {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprUnaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_NOT,
        :operand => boolean_leaf.value,
        :precedence_overridden => false)
    end
  }
/ arithmetic_leaf SYM_EQ arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_EQ,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf SYM_NE arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_NE,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf SYM_LT arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_LT,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf SYM_GT arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_GT,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf SYM_LE arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_LE,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf SYM_GE arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_GE,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ boolean_leaf SYM_AND boolean_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_AND,
        :left_operand => boolean_leaf.value,
        :right_operand => boolean_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ boolean_leaf SYM_OR boolean_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_OR,
        :left_operand => boolean_leaf.value,
        :right_operand => boolean_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ boolean_leaf SYM_XOR boolean_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_XOR,
        :left_operand => boolean_leaf.value,
        :right_operand => boolean_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ boolean_leaf SYM_IMPLIES boolean_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_IMPLIES,
        :left_operand => boolean_leaf.value,
        :right_operand => boolean_expression.value,
        :reference_type => 'Constraint')
    end
  }
end

rule boolean_leaf
  '(' boolean_expression ')' {
    def value
      boolean_expression.value
    end
  }
/ SYM_TRUE '' {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprLeaf.new(
        :type => 'Boolean',
        :item => true,
        :reference_type => 'CONSTANT')
    end
  }
/ SYM_FALSE '' {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprLeaf.new(
        :type => 'Boolean',
        :item => false,
        :reference_type => 'CONSTANT')
    end
  }
end

rule arithmetic_expression
  arithmetic_node '' {
    def value
      arithmetic_node.value
    end
  }
/ arithmetic_leaf '' {
    def value
      arithmetic_leaf.value
    end
  }
end

rule arithmetic_node
  arithmetic_leaf '+' arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_PLUS,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf '-' arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_MINUS,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf '*' arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_MULTIPLY,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf '/' arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_DIVIDE,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
/ arithmetic_leaf '^' arithmetic_expression {
    def value
      OpenEHR::AM::Archetype::Assertion::ExprBinaryOperator.new(
        :type => 'Boolean',
        :operator => OpenEHR::AM::Archetype::Assertion::OperatorKind::OP_EXP,
        :left_operand => arithmetic_leaf.value,
        :right_operand => arithmetic_expression.value,
        :reference_type => 'Constraint')
    end
  }
end

rule arithmetic_leaf
 '(' arithmetic_expression ')' space {
    def value
      arithmetic_expression.value
    end
  }
/ integer_value '' {
    def value
      OpenEHR::AM::Archetype::ConstraintModel::ExprLeaf.new(
        :type => 'Integer',
        :item => integer_value.value,
        :reference_type => 'CONSTANT')
    end
  }
/ real_value '' {
    def value
      OpenEHR::AM::Archetype::ConstraintModel::ExprLeaf.new(
        :type => 'Real',
        :item => real_value.value,
        :reference_type => 'CONSTANT')
    end
  }
/ absolute_path space {
    def value
      OpenEHR::AM::Archetype::ConstraintModel::ExprLeaf.new(
        :type => 'String',
        :item => absolute_path.value,
        :reference_type => 'CONSTANT')

    end
  }
end

# path block

    rule object_path
      movable_path '' {
        def value
          movable_path.value
        end
      }
    / absolute_path '' {
        def value
          absolute_path.value
        end
      }
    / relative_path '' {
        def value
          relative_path.value
        end
      }
    end

    rule movable_path
       SYM_MOVABLE_LEADER relative_path '' {
         def value
           text_value
         end
       }
    end

    rule absolute_path
      '/' relative_path? '' {
         def value
           text_value
         end
       }
    end

    rule relative_path
      path_segment ('/' path_segment)* '' {
        def value
          text_value
        end
      }
    end

    rule path_segment
      V_ATTRIBUTE_IDENTIFIER V_LOCAL_TERM_CODE_REF? '' {
        def value
          text_value
        end
      }
    end

    rule SYM_MOVABLE_LEADER
      '//'
    end
  end
end

end