# Source code for fontTools.voltLib.ast

    from fontTools.voltLib.error import VoltLibError
    from typing import NamedTuple

    class Pos(NamedTuple):
        adv: int
        dx: int
        dy: int
        adv_adjust_by: dict
        dx_adjust_by: dict
        dy_adjust_by: dict

        def __str__(self):
            res = " POS"
            for attr in ("adv", "dx", "dy"):
                value = getattr(self, attr)
                if value is not None:
                    res += f" {attr.upper()} {value}"
                    adjust_by = getattr(self, f"{attr}_adjust_by", {})
                    for size, adjustment in adjust_by.items():
                        res += f" ADJUST_BY {adjustment} AT {size}"
            res += " END_POS"
            return res

    class Element(object):
        def __init__(self, location=None):
            self.location = location

        def build(self, builder):
            pass

        def __str__(self):
            raise NotImplementedError

    class Statement(Element):
        pass

    class Expression(Element):
        pass

    class VoltFile(Statement):
        def __init__(self):
            Statement.__init__(self, location=None)
            self.statements = []

        def build(self, builder):
            for s in self.statements:
                s.build(builder)

        def __str__(self):
            return "\n" + "\n".join(str(s) for s in self.statements) + " END\n"

    class GlyphDefinition(Statement):
        def __init__(self, name, gid, gunicode, gtype, components, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.id = gid
            self.unicode = gunicode
            self.type = gtype
            self.components = components

        def __str__(self):
            res = f'DEF_GLYPH "{self.name}" ID {self.id}'
            if self.unicode is not None:
                if len(self.unicode) > 1:
                    unicodes = ",".join(f"U+{u:04X}" for u in self.unicode)
                    res += f' UNICODEVALUES "{unicodes}"'
                else:
                    res += f" UNICODE {self.unicode[0]}"
            if self.type is not None:
                res += f" TYPE {self.type}"
            if self.components is not None:
                res += f" COMPONENTS {self.components}"
            res += " END_GLYPH"
            return res

    class GroupDefinition(Statement):
        def __init__(self, name, enum, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.enum = enum
            self.glyphs_ = None

        def glyphSet(self, groups=None):
            if groups is not None and self.name in groups:
                raise VoltLibError(
                    'Group "%s" contains itself.' % (self.name), self.location
                )
            if self.glyphs_ is None:
                if groups is None:
                    groups = set({self.name})
                else:
                    groups.add(self.name)
                self.glyphs_ = self.enum.glyphSet(groups)
            return self.glyphs_

        def __str__(self):
            enum = self.enum and str(self.enum) or ""
            return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP'

    class GlyphName(Expression):
        """A single glyph name, such as cedilla."""

        def __init__(self, glyph, location=None):
            Expression.__init__(self, location)
            self.glyph = glyph

        def glyphSet(self):
            return (self.glyph,)

        def __str__(self):
            return f' GLYPH "{self.glyph}"'

    class Enum(Expression):
        """An enum"""

        def __init__(self, enum, location=None):
            Expression.__init__(self, location)
            self.enum = enum

        def __iter__(self):
            for e in self.glyphSet():
                yield e

        def glyphSet(self, groups=None):
            glyphs = []
            for element in self.enum:
                if isinstance(element, (GroupName, Enum)):
                    glyphs.extend(element.glyphSet(groups))
                else:
                    glyphs.extend(element.glyphSet())
            return tuple(glyphs)

        def __str__(self):
            enum = "".join(str(e) for e in self.enum)
            return f" ENUM{enum} END_ENUM"

    class GroupName(Expression):
        """A glyph group"""

        def __init__(self, group, parser, location=None):
            Expression.__init__(self, location)
            self.group = group
            self.parser_ = parser

        def glyphSet(self, groups=None):
            group = self.parser_.resolve_group(self.group)
            if group is not None:
                self.glyphs_ = group.glyphSet(groups)
                return self.glyphs_
            else:
                raise VoltLibError(
                    'Group "%s" is used but undefined.' % (self.group), self.location
                )

        def __str__(self):
            return f' GROUP "{self.group}"'

    class Range(Expression):
        """A glyph range"""

        def __init__(self, start, end, parser, location=None):
            Expression.__init__(self, location)
            self.start = start
            self.end = end
            self.parser = parser

        def glyphSet(self):
            return tuple(self.parser.glyph_range(self.start, self.end))

        def __str__(self):
            return f' RANGE "{self.start}" TO "{self.end}"'

    class ScriptDefinition(Statement):
        def __init__(self, name, tag, langs, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.tag = tag
            self.langs = langs

        def __str__(self):
            res = "DEF_SCRIPT"
            if self.name is not None:
                res += f' NAME "{self.name}"'
            res += f' TAG "{self.tag}"\n\n'
            for lang in self.langs:
                res += f"{lang}"
            res += "END_SCRIPT"
            return res

    class LangSysDefinition(Statement):
        def __init__(self, name, tag, features, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.tag = tag
            self.features = features

        def __str__(self):
            res = "DEF_LANGSYS"
            if self.name is not None:
                res += f' NAME "{self.name}"'
            res += f' TAG "{self.tag}"\n\n'
            for feature in self.features:
                res += f"{feature}"
            res += "END_LANGSYS\n"
            return res

    class FeatureDefinition(Statement):
        def __init__(self, name, tag, lookups, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.tag = tag
            self.lookups = lookups

        def __str__(self):
            res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n'
            res += " " + " ".join(f'LOOKUP "{l}"' for l in self.lookups) + "\n"
            res += "END_FEATURE\n"
            return res

    class LookupDefinition(Statement):
        def __init__(
            self,
            name,
            process_base,
            process_marks,
            mark_glyph_set,
            direction,
            reversal,
            comments,
            context,
            sub,
            pos,
            location=None,
        ):
            Statement.__init__(self, location)
            self.name = name
            self.process_base = process_base
            self.process_marks = process_marks
            self.mark_glyph_set = mark_glyph_set
            self.direction = direction
            self.reversal = reversal
            self.comments = comments
            self.context = context
            self.sub = sub
            self.pos = pos

        def __str__(self):
            res = f'DEF_LOOKUP "{self.name}"'
            res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}'
            if self.process_marks:
                res += " PROCESS_MARKS "
                if self.mark_glyph_set:
                    res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"'
                elif isinstance(self.process_marks, str):
                    res += f'"{self.process_marks}"'
                else:
                    res += "ALL"
            else:
                res += " SKIP_MARKS"
            if self.direction is not None:
                res += f" DIRECTION {self.direction}"
            if self.reversal:
                res += " REVERSAL"
            if self.comments is not None:
                comments = self.comments.replace("\n", r"\n")
                res += f'\nCOMMENTS "{comments}"'
            if self.context:
                res += "\n" + "\n".join(str(c) for c in self.context)
            else:
                res += "\nIN_CONTEXT\nEND_CONTEXT"
            if self.sub:
                res += f"\n{self.sub}"
            if self.pos:
                res += f"\n{self.pos}"
            return res

    class SubstitutionDefinition(Statement):
        def __init__(self, mapping, location=None):
            Statement.__init__(self, location)
            self.mapping = mapping

        def __str__(self):
            res = "AS_SUBSTITUTION\n"
            for src, dst in self.mapping.items():
                src = "".join(str(s) for s in src)
                dst = "".join(str(d) for d in dst)
                res += f"SUB{src}\nWITH{dst}\nEND_SUB\n"
            res += "END_SUBSTITUTION"
            return res

    class SubstitutionSingleDefinition(SubstitutionDefinition):
        pass

    class SubstitutionMultipleDefinition(SubstitutionDefinition):
        pass

    class SubstitutionLigatureDefinition(SubstitutionDefinition):
        pass

    class SubstitutionAlternateDefinition(SubstitutionDefinition):
        pass

    class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
        pass

    class PositionAttachDefinition(Statement):
        def __init__(self, coverage, coverage_to, location=None):
            Statement.__init__(self, location)
            self.coverage = coverage
            self.coverage_to = coverage_to

        def __str__(self):
            coverage = "".join(str(c) for c in self.coverage)
            res = f"AS_POSITION\nATTACH{coverage}\nTO"
            for coverage, anchor in self.coverage_to:
                coverage = "".join(str(c) for c in coverage)
                res += f'{coverage} AT ANCHOR "{anchor}"'
            res += "\nEND_ATTACH\nEND_POSITION"
            return res

    class PositionAttachCursiveDefinition(Statement):
        def __init__(self, coverages_exit, coverages_enter, location=None):
            Statement.__init__(self, location)
            self.coverages_exit = coverages_exit
            self.coverages_enter = coverages_enter

        def __str__(self):
            res = "AS_POSITION\nATTACH_CURSIVE"
            for coverage in self.coverages_exit:
                coverage = "".join(str(c) for c in coverage)
                res += f"\nEXIT {coverage}"
            for coverage in self.coverages_enter:
                coverage = "".join(str(c) for c in coverage)
                res += f"\nENTER {coverage}"
            res += "\nEND_ATTACH\nEND_POSITION"
            return res

    class PositionAdjustPairDefinition(Statement):
        def __init__(self, coverages_1, coverages_2, adjust_pair, location=None):
            Statement.__init__(self, location)
            self.coverages_1 = coverages_1
            self.coverages_2 = coverages_2
            self.adjust_pair = adjust_pair

        def __str__(self):
            res = "AS_POSITION\nADJUST_PAIR\n"
            for coverage in self.coverages_1:
                coverage = " ".join(str(c) for c in coverage)
                res += f" FIRST {coverage}"
            res += "\n"
            for coverage in self.coverages_2:
                coverage = " ".join(str(c) for c in coverage)
                res += f" SECOND {coverage}"
            res += "\n"
            for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items():
                res += f" {id_1} {id_2} BY{pos_1}{pos_2}\n"
            res += "\nEND_ADJUST\nEND_POSITION"
            return res

    class PositionAdjustSingleDefinition(Statement):
        def __init__(self, adjust_single, location=None):
            Statement.__init__(self, location)
            self.adjust_single = adjust_single

        def __str__(self):
            res = "AS_POSITION\nADJUST_SINGLE"
            for coverage, pos in self.adjust_single:
                coverage = "".join(str(c) for c in coverage)
                res += f"{coverage} BY{pos}"
            res += "\nEND_ADJUST\nEND_POSITION"
            return res

    class ContextDefinition(Statement):
        def __init__(self, ex_or_in, left=None, right=None, location=None):
            Statement.__init__(self, location)
            self.ex_or_in = ex_or_in
            self.left = left if left is not None else []
            self.right = right if right is not None else []

        def __str__(self):
            res = self.ex_or_in + "\n"
            for coverage in self.left:
                coverage = "".join(str(c) for c in coverage)
                res += f" LEFT{coverage}\n"
            for coverage in self.right:
                coverage = "".join(str(c) for c in coverage)
                res += f" RIGHT{coverage}\n"
            res += "END_CONTEXT"
            return res

    class AnchorDefinition(Statement):
        def __init__(self, name, gid, glyph_name, component, locked, pos, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.gid = gid
            self.glyph_name = glyph_name
            self.component = component
            self.locked = locked
            self.pos = pos

        def __str__(self):
            locked = self.locked and " LOCKED" or ""
            return (
                f'DEF_ANCHOR "{self.name}"'
                f" ON {self.gid}"
                f" GLYPH {self.glyph_name}"
                f" COMPONENT {self.component}"
                f"{locked}"
                f" AT {self.pos} END_ANCHOR"
            )

    class SettingDefinition(Statement):
        def __init__(self, name, value, location=None):
            Statement.__init__(self, location)
            self.name = name
            self.value = value

        def __str__(self):
            if self.value is True:
                return f"{self.name}"
            if isinstance(self.value, (tuple, list)):
                value = " ".join(str(v) for v in self.value)
                return f"{self.name} {value}"
            return f"{self.name} {self.value}"