Source code for flexicon.code.Lexicon.morphosyntax_analysis

#
#   morphosyntax_analysis.py
#
#   Class: MorphosyntaxAnalysis
#          Wrapper for morphosyntactic analysis objects providing unified interface
#          access across multiple concrete types.
#
#   Platform: Python.NET
#             FieldWorks Version 9+
#
#   Copyright 2025
#

"""
Wrapper class for morphosyntactic analysis (MSA) objects with unified interface.

This module provides MorphosyntaxAnalysis, a wrapper class that transparently
handles the four concrete types of morphosyntactic analyses:
- MoStemMsa: Stem (root) analysis with Part of Speech
- MoDerivAffMsa: Derivational affix analysis with from/to Part of Speech
- MoInflAffMsa: Inflectional affix analysis with Part of Speech and slots
- MoUnclassifiedAffixMsa: Unclassified affix analysis

All share base interface IMoMorphSynAnalysis.

Problem:
    MSAs have different properties depending on their concrete type:

    - All have PartOfSpeechRA or similar properties for grammatical category
    - MoDerivAffMsa has FromPartOfSpeechRA and ToPartOfSpeechRA (input/output POS)
    - MoInflAffMsa has PartOfSpeechRA and inflection class references
    - Users working with mixed collections need to check ClassName and cast to
      access type-specific properties, which is error-prone and verbose.

Solution:
    MorphosyntaxAnalysis wrapper provides:

    - Simple properties for common features (pos_main, class_type)
    - Capability check properties (is_stem_msa, is_deriv_aff_msa, etc.)
    - Property access that works across all types
    - Optional: Methods for advanced users who know C# types

Example::

    from flexlibs2.code.Lexicon.morphosyntax_analysis import MorphosyntaxAnalysis

    # Wrap an MSA from entry.MorphoSyntaxAnalysesOC
    msa = entry.MorphoSyntaxAnalysesOC[0]
    wrapped = MorphosyntaxAnalysis(msa)

    # Access common properties
    if wrapped.is_stem_msa:
        print(f"Stem with POS: {wrapped.pos_main}")

    if wrapped.is_deriv_aff_msa:
        from_pos = wrapped.pos_from
        to_pos = wrapped.pos_to
        print(f"Derives: {from_pos} -> {to_pos}")

    # Check capabilities
    if wrapped.has_from_pos:
        print(f"From POS: {wrapped.pos_from}")

    # Optional: Advanced users can access concrete types
    if wrapped.as_stem_msa():
        concrete = wrapped.as_stem_msa()
        # Use concrete interface for advanced operations
"""

from ..Shared.wrapper_base import LCMObjectWrapper
from ..lcm_casting import cast_to_concrete, get_pos_from_msa, get_from_pos_from_msa


[docs] class MorphosyntaxAnalysis(LCMObjectWrapper): """ Wrapper for morphosyntactic analysis objects providing unified interface access. Handles the four concrete types of MSAs (MoStemMsa, MoDerivAffMsa, MoInflAffMsa, MoUnclassifiedAffixMsa) transparently, providing common properties and capability checks without exposing ClassName or casting. Attributes: _obj: The base interface object (IMoMorphSynAnalysis) _concrete: The concrete type object (IMoStemMsa, IMoDerivAffMsa, IMoInflAffMsa, or IMoUnclassifiedAffixMsa) Example:: msa = entry.MorphoSyntaxAnalysesOC[0] wrapped = MorphosyntaxAnalysis(msa) print(wrapped.class_type) if wrapped.is_stem_msa: print(wrapped.pos_main) """ def __init__(self, lcm_msa): """ Initialize MorphosyntaxAnalysis wrapper with an MSA object. Args: lcm_msa: An IMoMorphSynAnalysis object (or derived type). Typically from entry.MorphoSyntaxAnalysesOC. Example:: msa = entry.MorphoSyntaxAnalysesOC[0] wrapped = MorphosyntaxAnalysis(msa) """ super().__init__(lcm_msa) # ========== Type Check Properties ========== @property def is_stem_msa(self): """ Check if this is a stem MSA (MoStemMsa). Returns: bool: True if this is a MoStemMsa object. Example:: if wrapped.is_stem_msa: pos = wrapped.pos_main print(f"Stem POS: {pos}") """ return self.class_type == "MoStemMsa" @property def is_deriv_aff_msa(self): """ Check if this is a derivational affix MSA (MoDerivAffMsa). Returns: bool: True if this is a MoDerivAffMsa object. Example:: if wrapped.is_deriv_aff_msa: from_pos = wrapped.pos_from to_pos = wrapped.pos_to print(f"Derives: {from_pos} -> {to_pos}") """ return self.class_type == "MoDerivAffMsa" @property def is_infl_aff_msa(self): """ Check if this is an inflectional affix MSA (MoInflAffMsa). Returns: bool: True if this is a MoInflAffMsa object. Example:: if wrapped.is_infl_aff_msa: pos = wrapped.pos_main print(f"Inflectional affix for POS: {pos}") """ return self.class_type == "MoInflAffMsa" @property def is_unclassified_aff_msa(self): """ Check if this is an unclassified affix MSA (MoUnclassifiedAffixMsa). Returns: bool: True if this is a MoUnclassifiedAffixMsa object. Example:: if wrapped.is_unclassified_aff_msa: pos = wrapped.pos_main print(f"Unclassified affix with POS: {pos}") """ return self.class_type == "MoUnclassifiedAffixMsa" # ========== Common Properties (work across most MSA types) ========== @property def pos_main(self): """ Get the main Part of Speech for this MSA. Returns the PartOfSpeechRA for stem and inflectional affixes, and ToPartOfSpeechRA for derivational affixes (output POS). Returns: IPartOfSpeech or None: The main POS reference, or None if not set. Example:: if wrapped.pos_main: pos_name = wrapped.pos_main.Name.BestAnalysisAlternative.Text print(f"POS: {pos_name}") Notes: - For MoStemMsa, MoInflAffMsa, MoUnclassifiedAffixMsa: PartOfSpeechRA - For MoDerivAffMsa: ToPartOfSpeechRA (output POS) - Use pos_from for derivational affix input POS """ return get_pos_from_msa(self._obj) @property def has_from_pos(self): """ Check if this MSA has a "from" Part of Speech (input POS). Only meaningful for derivational affixes, which show what POS something derives from. Returns False for all other MSA types. Returns: bool: True if this is a MoDerivAffMsa with FromPartOfSpeechRA set. Example:: if wrapped.has_from_pos: from_pos = wrapped.pos_from to_pos = wrapped.pos_to print(f"Derives: {from_pos} -> {to_pos}") Notes: - Only MoDerivAffMsa has FromPartOfSpeechRA - Always False for MoStemMsa, MoInflAffMsa, MoUnclassifiedAffixMsa """ if not self.is_deriv_aff_msa: return False try: return get_from_pos_from_msa(self._obj) is not None except Exception: return False @property def pos_from(self): """ Get the "from" Part of Speech for derivational affixes. Only meaningful for MoDerivAffMsa. Shows the POS before derivation. Returns None for all other MSA types. Returns: IPartOfSpeech or None: The input POS for derivational affixes, or None if not a derivational affix or not set. Example:: if wrapped.has_from_pos: from_pos = wrapped.pos_from to_pos = wrapped.pos_to print(f"Derives {from_pos.Name} to {to_pos.Name}") Notes: - Only meaningful for MoDerivAffMsa - Returns None for MoStemMsa, MoInflAffMsa, MoUnclassifiedAffixMsa - Use pos_to to get the output POS """ if not self.is_deriv_aff_msa: return None return get_from_pos_from_msa(self._obj) @property def pos_to(self): """ Get the "to" Part of Speech for derivational affixes. For derivational affixes, this is the output POS (what you get after applying the derivation). For other MSA types, returns the same as pos_main. Returns: IPartOfSpeech or None: The output POS for derivational affixes, or the main POS for other types, or None if not set. Example:: if wrapped.is_deriv_aff_msa: from_pos = wrapped.pos_from to_pos = wrapped.pos_to print(f"Change: {from_pos} -> {to_pos}") Notes: - For MoDerivAffMsa: ToPartOfSpeechRA (output after derivation) - For other types: Same as pos_main """ if self.is_deriv_aff_msa: return get_pos_from_msa(self._obj) # ToPartOfSpeechRA else: return self.pos_main # ========== Advanced: Direct C# class access (optional for power users) ==========
[docs] def as_stem_msa(self): """ Cast to IMoStemMsa if this is a stem MSA. For advanced users who need direct access to the C# concrete interface. Returns None if this is not a MoStemMsa. Returns: IMoStemMsa or None: The concrete interface if this is a MoStemMsa, None otherwise. Example:: if wrapped.as_stem_msa(): concrete = wrapped.as_stem_msa() # Can now access IMoStemMsa-specific methods/properties # Advanced operations... Notes: - For users who know C# interfaces and want advanced control - Most users should use properties like pos_main instead - Only useful if you need to call methods or access properties that aren't exposed through the wrapper """ if self.class_type == "MoStemMsa": return self._concrete return None
[docs] def as_deriv_aff_msa(self): """ Cast to IMoDerivAffMsa if this is a derivational affix MSA. For advanced users who need direct access to the C# concrete interface. Returns None if this is not a MoDerivAffMsa. Returns: IMoDerivAffMsa or None: The concrete interface if this is a MoDerivAffMsa, None otherwise. Example:: if wrapped.as_deriv_aff_msa(): concrete = wrapped.as_deriv_aff_msa() # Can now access IMoDerivAffMsa-specific methods/properties Notes: - For users who know C# interfaces and want advanced control - Most users should use properties like pos_from and pos_to instead """ if self.class_type == "MoDerivAffMsa": return self._concrete return None
[docs] def as_infl_aff_msa(self): """ Cast to IMoInflAffMsa if this is an inflectional affix MSA. For advanced users who need direct access to the C# concrete interface. Returns None if this is not a MoInflAffMsa. Returns: IMoInflAffMsa or None: The concrete interface if this is a MoInflAffMsa, None otherwise. Example:: if wrapped.as_infl_aff_msa(): concrete = wrapped.as_infl_aff_msa() # Can now access IMoInflAffMsa-specific methods/properties Notes: - For users who know C# interfaces and want advanced control - Most users should use properties like pos_main instead """ if self.class_type == "MoInflAffMsa": return self._concrete return None
[docs] def as_unclassified_aff_msa(self): """ Cast to IMoUnclassifiedAffixMsa if this is an unclassified affix MSA. For advanced users who need direct access to the C# concrete interface. Returns None if this is not a MoUnclassifiedAffixMsa. Returns: IMoUnclassifiedAffixMsa or None: The concrete interface if this is a MoUnclassifiedAffixMsa, None otherwise. Example:: if wrapped.as_unclassified_aff_msa(): concrete = wrapped.as_unclassified_aff_msa() # Can now access IMoUnclassifiedAffixMsa-specific methods/properties Notes: - For users who know C# interfaces and want advanced control - Most users should use properties like pos_main instead """ if self.class_type == "MoUnclassifiedAffixMsa": return self._concrete return None
@property def concrete(self): """ Get the raw concrete interface object. For advanced users who need to access the underlying C# interface directly without going through wrapper properties. Returns: The concrete interface object (IMoStemMsa, IMoDerivAffMsa, IMoInflAffMsa, or IMoUnclassifiedAffixMsa depending on the MSA's actual type). Example:: # Direct access to concrete interface concrete = msa_obj.concrete pos = concrete.PartOfSpeechRA # MSA type-specific property Notes: - For power users only - Bypasses the wrapper's abstraction - Normal users should prefer wrapper properties like is_stem_msa, pos_main, etc. """ return self._concrete def __repr__(self): """String representation showing MSA type.""" return f"MorphosyntaxAnalysis({self.class_type})" def __str__(self): """Human-readable description.""" pos = self.pos_main pos_name = "" if pos: try: pos_name = f" [{pos.Name.BestAnalysisAlternative.Text}]" except Exception: pos_name = " [unknown POS]" return f"{self.class_type}{pos_name}"