You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			665 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			665 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
import fontTools.voltLib.ast as ast
 | 
						|
from fontTools.voltLib.lexer import Lexer
 | 
						|
from fontTools.voltLib.error import VoltLibError
 | 
						|
from io import open
 | 
						|
 | 
						|
PARSE_FUNCS = {
 | 
						|
    "DEF_GLYPH": "parse_def_glyph_",
 | 
						|
    "DEF_GROUP": "parse_def_group_",
 | 
						|
    "DEF_SCRIPT": "parse_def_script_",
 | 
						|
    "DEF_LOOKUP": "parse_def_lookup_",
 | 
						|
    "DEF_ANCHOR": "parse_def_anchor_",
 | 
						|
    "GRID_PPEM": "parse_ppem_",
 | 
						|
    "PRESENTATION_PPEM": "parse_ppem_",
 | 
						|
    "PPOSITIONING_PPEM": "parse_ppem_",
 | 
						|
    "COMPILER_USEEXTENSIONLOOKUPS": "parse_noarg_option_",
 | 
						|
    "COMPILER_USEPAIRPOSFORMAT2": "parse_noarg_option_",
 | 
						|
    "CMAP_FORMAT": "parse_cmap_format",
 | 
						|
    "DO_NOT_TOUCH_CMAP": "parse_noarg_option_",
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
class Parser(object):
 | 
						|
    def __init__(self, path):
 | 
						|
        self.doc_ = ast.VoltFile()
 | 
						|
        self.glyphs_ = OrderedSymbolTable()
 | 
						|
        self.groups_ = SymbolTable()
 | 
						|
        self.anchors_ = {}  # dictionary of SymbolTable() keyed by glyph
 | 
						|
        self.scripts_ = SymbolTable()
 | 
						|
        self.langs_ = SymbolTable()
 | 
						|
        self.lookups_ = SymbolTable()
 | 
						|
        self.next_token_type_, self.next_token_ = (None, None)
 | 
						|
        self.next_token_location_ = None
 | 
						|
        self.make_lexer_(path)
 | 
						|
        self.advance_lexer_()
 | 
						|
 | 
						|
    def make_lexer_(self, file_or_path):
 | 
						|
        if hasattr(file_or_path, "read"):
 | 
						|
            filename = getattr(file_or_path, "name", None)
 | 
						|
            data = file_or_path.read()
 | 
						|
        else:
 | 
						|
            filename = file_or_path
 | 
						|
            with open(file_or_path, "r") as f:
 | 
						|
                data = f.read()
 | 
						|
        self.lexer_ = Lexer(data, filename)
 | 
						|
 | 
						|
    def parse(self):
 | 
						|
        statements = self.doc_.statements
 | 
						|
        while self.next_token_type_ is not None:
 | 
						|
            self.advance_lexer_()
 | 
						|
            if self.cur_token_ in PARSE_FUNCS.keys():
 | 
						|
                func = getattr(self, PARSE_FUNCS[self.cur_token_])
 | 
						|
                statements.append(func())
 | 
						|
            elif self.is_cur_keyword_("END"):
 | 
						|
                break
 | 
						|
            else:
 | 
						|
                raise VoltLibError(
 | 
						|
                    "Expected " + ", ".join(sorted(PARSE_FUNCS.keys())),
 | 
						|
                    self.cur_token_location_,
 | 
						|
                )
 | 
						|
        return self.doc_
 | 
						|
 | 
						|
    def parse_def_glyph_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_GLYPH")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = self.expect_string_()
 | 
						|
        self.expect_keyword_("ID")
 | 
						|
        gid = self.expect_number_()
 | 
						|
        if gid < 0:
 | 
						|
            raise VoltLibError("Invalid glyph ID", self.cur_token_location_)
 | 
						|
        gunicode = None
 | 
						|
        if self.next_token_ == "UNICODE":
 | 
						|
            self.expect_keyword_("UNICODE")
 | 
						|
            gunicode = [self.expect_number_()]
 | 
						|
            if gunicode[0] < 0:
 | 
						|
                raise VoltLibError("Invalid glyph UNICODE", self.cur_token_location_)
 | 
						|
        elif self.next_token_ == "UNICODEVALUES":
 | 
						|
            self.expect_keyword_("UNICODEVALUES")
 | 
						|
            gunicode = self.parse_unicode_values_()
 | 
						|
        gtype = None
 | 
						|
        if self.next_token_ == "TYPE":
 | 
						|
            self.expect_keyword_("TYPE")
 | 
						|
            gtype = self.expect_name_()
 | 
						|
            assert gtype in ("BASE", "LIGATURE", "MARK", "COMPONENT")
 | 
						|
        components = None
 | 
						|
        if self.next_token_ == "COMPONENTS":
 | 
						|
            self.expect_keyword_("COMPONENTS")
 | 
						|
            components = self.expect_number_()
 | 
						|
        self.expect_keyword_("END_GLYPH")
 | 
						|
        if self.glyphs_.resolve(name) is not None:
 | 
						|
            raise VoltLibError(
 | 
						|
                'Glyph "%s" (gid %i) already defined' % (name, gid), location
 | 
						|
            )
 | 
						|
        def_glyph = ast.GlyphDefinition(
 | 
						|
            name, gid, gunicode, gtype, components, location=location
 | 
						|
        )
 | 
						|
        self.glyphs_.define(name, def_glyph)
 | 
						|
        return def_glyph
 | 
						|
 | 
						|
    def parse_def_group_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_GROUP")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = self.expect_string_()
 | 
						|
        enum = None
 | 
						|
        if self.next_token_ == "ENUM":
 | 
						|
            enum = self.parse_enum_()
 | 
						|
        self.expect_keyword_("END_GROUP")
 | 
						|
        if self.groups_.resolve(name) is not None:
 | 
						|
            raise VoltLibError(
 | 
						|
                'Glyph group "%s" already defined, '
 | 
						|
                "group names are case insensitive" % name,
 | 
						|
                location,
 | 
						|
            )
 | 
						|
        def_group = ast.GroupDefinition(name, enum, location=location)
 | 
						|
        self.groups_.define(name, def_group)
 | 
						|
        return def_group
 | 
						|
 | 
						|
    def parse_def_script_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_SCRIPT")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = None
 | 
						|
        if self.next_token_ == "NAME":
 | 
						|
            self.expect_keyword_("NAME")
 | 
						|
            name = self.expect_string_()
 | 
						|
        self.expect_keyword_("TAG")
 | 
						|
        tag = self.expect_string_()
 | 
						|
        if self.scripts_.resolve(tag) is not None:
 | 
						|
            raise VoltLibError(
 | 
						|
                'Script "%s" already defined, '
 | 
						|
                "script tags are case insensitive" % tag,
 | 
						|
                location,
 | 
						|
            )
 | 
						|
        self.langs_.enter_scope()
 | 
						|
        langs = []
 | 
						|
        while self.next_token_ != "END_SCRIPT":
 | 
						|
            self.advance_lexer_()
 | 
						|
            lang = self.parse_langsys_()
 | 
						|
            self.expect_keyword_("END_LANGSYS")
 | 
						|
            if self.langs_.resolve(lang.tag) is not None:
 | 
						|
                raise VoltLibError(
 | 
						|
                    'Language "%s" already defined in script "%s", '
 | 
						|
                    "language tags are case insensitive" % (lang.tag, tag),
 | 
						|
                    location,
 | 
						|
                )
 | 
						|
            self.langs_.define(lang.tag, lang)
 | 
						|
            langs.append(lang)
 | 
						|
        self.expect_keyword_("END_SCRIPT")
 | 
						|
        self.langs_.exit_scope()
 | 
						|
        def_script = ast.ScriptDefinition(name, tag, langs, location=location)
 | 
						|
        self.scripts_.define(tag, def_script)
 | 
						|
        return def_script
 | 
						|
 | 
						|
    def parse_langsys_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_LANGSYS")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = None
 | 
						|
        if self.next_token_ == "NAME":
 | 
						|
            self.expect_keyword_("NAME")
 | 
						|
            name = self.expect_string_()
 | 
						|
        self.expect_keyword_("TAG")
 | 
						|
        tag = self.expect_string_()
 | 
						|
        features = []
 | 
						|
        while self.next_token_ != "END_LANGSYS":
 | 
						|
            self.advance_lexer_()
 | 
						|
            feature = self.parse_feature_()
 | 
						|
            self.expect_keyword_("END_FEATURE")
 | 
						|
            features.append(feature)
 | 
						|
        def_langsys = ast.LangSysDefinition(name, tag, features, location=location)
 | 
						|
        return def_langsys
 | 
						|
 | 
						|
    def parse_feature_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_FEATURE")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        self.expect_keyword_("NAME")
 | 
						|
        name = self.expect_string_()
 | 
						|
        self.expect_keyword_("TAG")
 | 
						|
        tag = self.expect_string_()
 | 
						|
        lookups = []
 | 
						|
        while self.next_token_ != "END_FEATURE":
 | 
						|
            # self.advance_lexer_()
 | 
						|
            self.expect_keyword_("LOOKUP")
 | 
						|
            lookup = self.expect_string_()
 | 
						|
            lookups.append(lookup)
 | 
						|
        feature = ast.FeatureDefinition(name, tag, lookups, location=location)
 | 
						|
        return feature
 | 
						|
 | 
						|
    def parse_def_lookup_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_LOOKUP")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = self.expect_string_()
 | 
						|
        if not name[0].isalpha():
 | 
						|
            raise VoltLibError(
 | 
						|
                'Lookup name "%s" must start with a letter' % name, location
 | 
						|
            )
 | 
						|
        if self.lookups_.resolve(name) is not None:
 | 
						|
            raise VoltLibError(
 | 
						|
                'Lookup "%s" already defined, '
 | 
						|
                "lookup names are case insensitive" % name,
 | 
						|
                location,
 | 
						|
            )
 | 
						|
        process_base = True
 | 
						|
        if self.next_token_ == "PROCESS_BASE":
 | 
						|
            self.advance_lexer_()
 | 
						|
        elif self.next_token_ == "SKIP_BASE":
 | 
						|
            self.advance_lexer_()
 | 
						|
            process_base = False
 | 
						|
        process_marks = True
 | 
						|
        mark_glyph_set = None
 | 
						|
        if self.next_token_ == "PROCESS_MARKS":
 | 
						|
            self.advance_lexer_()
 | 
						|
            if self.next_token_ == "MARK_GLYPH_SET":
 | 
						|
                self.advance_lexer_()
 | 
						|
                mark_glyph_set = self.expect_string_()
 | 
						|
            elif self.next_token_ == "ALL":
 | 
						|
                self.advance_lexer_()
 | 
						|
            elif self.next_token_ == "NONE":
 | 
						|
                self.advance_lexer_()
 | 
						|
                process_marks = False
 | 
						|
            elif self.next_token_type_ == Lexer.STRING:
 | 
						|
                process_marks = self.expect_string_()
 | 
						|
            else:
 | 
						|
                raise VoltLibError(
 | 
						|
                    "Expected ALL, NONE, MARK_GLYPH_SET or an ID. "
 | 
						|
                    "Got %s" % (self.next_token_type_),
 | 
						|
                    location,
 | 
						|
                )
 | 
						|
        elif self.next_token_ == "SKIP_MARKS":
 | 
						|
            self.advance_lexer_()
 | 
						|
            process_marks = False
 | 
						|
        direction = None
 | 
						|
        if self.next_token_ == "DIRECTION":
 | 
						|
            self.expect_keyword_("DIRECTION")
 | 
						|
            direction = self.expect_name_()
 | 
						|
            assert direction in ("LTR", "RTL")
 | 
						|
        reversal = None
 | 
						|
        if self.next_token_ == "REVERSAL":
 | 
						|
            self.expect_keyword_("REVERSAL")
 | 
						|
            reversal = True
 | 
						|
        comments = None
 | 
						|
        if self.next_token_ == "COMMENTS":
 | 
						|
            self.expect_keyword_("COMMENTS")
 | 
						|
            comments = self.expect_string_().replace(r"\n", "\n")
 | 
						|
        context = []
 | 
						|
        while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"):
 | 
						|
            context = self.parse_context_()
 | 
						|
        as_pos_or_sub = self.expect_name_()
 | 
						|
        sub = None
 | 
						|
        pos = None
 | 
						|
        if as_pos_or_sub == "AS_SUBSTITUTION":
 | 
						|
            sub = self.parse_substitution_(reversal)
 | 
						|
        elif as_pos_or_sub == "AS_POSITION":
 | 
						|
            pos = self.parse_position_()
 | 
						|
        else:
 | 
						|
            raise VoltLibError(
 | 
						|
                "Expected AS_SUBSTITUTION or AS_POSITION. " "Got %s" % (as_pos_or_sub),
 | 
						|
                location,
 | 
						|
            )
 | 
						|
        def_lookup = ast.LookupDefinition(
 | 
						|
            name,
 | 
						|
            process_base,
 | 
						|
            process_marks,
 | 
						|
            mark_glyph_set,
 | 
						|
            direction,
 | 
						|
            reversal,
 | 
						|
            comments,
 | 
						|
            context,
 | 
						|
            sub,
 | 
						|
            pos,
 | 
						|
            location=location,
 | 
						|
        )
 | 
						|
        self.lookups_.define(name, def_lookup)
 | 
						|
        return def_lookup
 | 
						|
 | 
						|
    def parse_context_(self):
 | 
						|
        location = self.cur_token_location_
 | 
						|
        contexts = []
 | 
						|
        while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"):
 | 
						|
            side = None
 | 
						|
            coverage = None
 | 
						|
            ex_or_in = self.expect_name_()
 | 
						|
            # side_contexts = [] # XXX
 | 
						|
            if self.next_token_ != "END_CONTEXT":
 | 
						|
                left = []
 | 
						|
                right = []
 | 
						|
                while self.next_token_ in ("LEFT", "RIGHT"):
 | 
						|
                    side = self.expect_name_()
 | 
						|
                    coverage = self.parse_coverage_()
 | 
						|
                    if side == "LEFT":
 | 
						|
                        left.append(coverage)
 | 
						|
                    else:
 | 
						|
                        right.append(coverage)
 | 
						|
                self.expect_keyword_("END_CONTEXT")
 | 
						|
                context = ast.ContextDefinition(
 | 
						|
                    ex_or_in, left, right, location=location
 | 
						|
                )
 | 
						|
                contexts.append(context)
 | 
						|
            else:
 | 
						|
                self.expect_keyword_("END_CONTEXT")
 | 
						|
        return contexts
 | 
						|
 | 
						|
    def parse_substitution_(self, reversal):
 | 
						|
        assert self.is_cur_keyword_("AS_SUBSTITUTION")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        src = []
 | 
						|
        dest = []
 | 
						|
        if self.next_token_ != "SUB":
 | 
						|
            raise VoltLibError("Expected SUB", location)
 | 
						|
        while self.next_token_ == "SUB":
 | 
						|
            self.expect_keyword_("SUB")
 | 
						|
            src.append(self.parse_coverage_())
 | 
						|
            self.expect_keyword_("WITH")
 | 
						|
            dest.append(self.parse_coverage_())
 | 
						|
            self.expect_keyword_("END_SUB")
 | 
						|
        self.expect_keyword_("END_SUBSTITUTION")
 | 
						|
        max_src = max([len(cov) for cov in src])
 | 
						|
        max_dest = max([len(cov) for cov in dest])
 | 
						|
 | 
						|
        # many to many or mixed is invalid
 | 
						|
        if max_src > 1 and max_dest > 1:
 | 
						|
            raise VoltLibError("Invalid substitution type", location)
 | 
						|
 | 
						|
        mapping = dict(zip(tuple(src), tuple(dest)))
 | 
						|
        if max_src == 1 and max_dest == 1:
 | 
						|
            # Alternate substitutions are represented by adding multiple
 | 
						|
            # substitutions for the same glyph, so we detect that here
 | 
						|
            glyphs = [x.glyphSet() for cov in src for x in cov]  # flatten src
 | 
						|
            if len(set(glyphs)) != len(glyphs):  # src has duplicates
 | 
						|
                sub = ast.SubstitutionAlternateDefinition(mapping, location=location)
 | 
						|
            else:
 | 
						|
                if reversal:
 | 
						|
                    # Reversal is valid only for single glyph substitutions
 | 
						|
                    # and VOLT ignores it otherwise.
 | 
						|
                    sub = ast.SubstitutionReverseChainingSingleDefinition(
 | 
						|
                        mapping, location=location
 | 
						|
                    )
 | 
						|
                else:
 | 
						|
                    sub = ast.SubstitutionSingleDefinition(mapping, location=location)
 | 
						|
        elif max_src == 1 and max_dest > 1:
 | 
						|
            sub = ast.SubstitutionMultipleDefinition(mapping, location=location)
 | 
						|
        elif max_src > 1 and max_dest == 1:
 | 
						|
            sub = ast.SubstitutionLigatureDefinition(mapping, location=location)
 | 
						|
        return sub
 | 
						|
 | 
						|
    def parse_position_(self):
 | 
						|
        assert self.is_cur_keyword_("AS_POSITION")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        pos_type = self.expect_name_()
 | 
						|
        if pos_type not in ("ATTACH", "ATTACH_CURSIVE", "ADJUST_PAIR", "ADJUST_SINGLE"):
 | 
						|
            raise VoltLibError(
 | 
						|
                "Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE", location
 | 
						|
            )
 | 
						|
        if pos_type == "ATTACH":
 | 
						|
            position = self.parse_attach_()
 | 
						|
        elif pos_type == "ATTACH_CURSIVE":
 | 
						|
            position = self.parse_attach_cursive_()
 | 
						|
        elif pos_type == "ADJUST_PAIR":
 | 
						|
            position = self.parse_adjust_pair_()
 | 
						|
        elif pos_type == "ADJUST_SINGLE":
 | 
						|
            position = self.parse_adjust_single_()
 | 
						|
        self.expect_keyword_("END_POSITION")
 | 
						|
        return position
 | 
						|
 | 
						|
    def parse_attach_(self):
 | 
						|
        assert self.is_cur_keyword_("ATTACH")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        coverage = self.parse_coverage_()
 | 
						|
        coverage_to = []
 | 
						|
        self.expect_keyword_("TO")
 | 
						|
        while self.next_token_ != "END_ATTACH":
 | 
						|
            cov = self.parse_coverage_()
 | 
						|
            self.expect_keyword_("AT")
 | 
						|
            self.expect_keyword_("ANCHOR")
 | 
						|
            anchor_name = self.expect_string_()
 | 
						|
            coverage_to.append((cov, anchor_name))
 | 
						|
        self.expect_keyword_("END_ATTACH")
 | 
						|
        position = ast.PositionAttachDefinition(
 | 
						|
            coverage, coverage_to, location=location
 | 
						|
        )
 | 
						|
        return position
 | 
						|
 | 
						|
    def parse_attach_cursive_(self):
 | 
						|
        assert self.is_cur_keyword_("ATTACH_CURSIVE")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        coverages_exit = []
 | 
						|
        coverages_enter = []
 | 
						|
        while self.next_token_ != "ENTER":
 | 
						|
            self.expect_keyword_("EXIT")
 | 
						|
            coverages_exit.append(self.parse_coverage_())
 | 
						|
        while self.next_token_ != "END_ATTACH":
 | 
						|
            self.expect_keyword_("ENTER")
 | 
						|
            coverages_enter.append(self.parse_coverage_())
 | 
						|
        self.expect_keyword_("END_ATTACH")
 | 
						|
        position = ast.PositionAttachCursiveDefinition(
 | 
						|
            coverages_exit, coverages_enter, location=location
 | 
						|
        )
 | 
						|
        return position
 | 
						|
 | 
						|
    def parse_adjust_pair_(self):
 | 
						|
        assert self.is_cur_keyword_("ADJUST_PAIR")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        coverages_1 = []
 | 
						|
        coverages_2 = []
 | 
						|
        adjust_pair = {}
 | 
						|
        while self.next_token_ == "FIRST":
 | 
						|
            self.advance_lexer_()
 | 
						|
            coverage_1 = self.parse_coverage_()
 | 
						|
            coverages_1.append(coverage_1)
 | 
						|
        while self.next_token_ == "SECOND":
 | 
						|
            self.advance_lexer_()
 | 
						|
            coverage_2 = self.parse_coverage_()
 | 
						|
            coverages_2.append(coverage_2)
 | 
						|
        while self.next_token_ != "END_ADJUST":
 | 
						|
            id_1 = self.expect_number_()
 | 
						|
            id_2 = self.expect_number_()
 | 
						|
            self.expect_keyword_("BY")
 | 
						|
            pos_1 = self.parse_pos_()
 | 
						|
            pos_2 = self.parse_pos_()
 | 
						|
            adjust_pair[(id_1, id_2)] = (pos_1, pos_2)
 | 
						|
        self.expect_keyword_("END_ADJUST")
 | 
						|
        position = ast.PositionAdjustPairDefinition(
 | 
						|
            coverages_1, coverages_2, adjust_pair, location=location
 | 
						|
        )
 | 
						|
        return position
 | 
						|
 | 
						|
    def parse_adjust_single_(self):
 | 
						|
        assert self.is_cur_keyword_("ADJUST_SINGLE")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        adjust_single = []
 | 
						|
        while self.next_token_ != "END_ADJUST":
 | 
						|
            coverages = self.parse_coverage_()
 | 
						|
            self.expect_keyword_("BY")
 | 
						|
            pos = self.parse_pos_()
 | 
						|
            adjust_single.append((coverages, pos))
 | 
						|
        self.expect_keyword_("END_ADJUST")
 | 
						|
        position = ast.PositionAdjustSingleDefinition(adjust_single, location=location)
 | 
						|
        return position
 | 
						|
 | 
						|
    def parse_def_anchor_(self):
 | 
						|
        assert self.is_cur_keyword_("DEF_ANCHOR")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = self.expect_string_()
 | 
						|
        self.expect_keyword_("ON")
 | 
						|
        gid = self.expect_number_()
 | 
						|
        self.expect_keyword_("GLYPH")
 | 
						|
        glyph_name = self.expect_name_()
 | 
						|
        self.expect_keyword_("COMPONENT")
 | 
						|
        component = self.expect_number_()
 | 
						|
        # check for duplicate anchor names on this glyph
 | 
						|
        if glyph_name in self.anchors_:
 | 
						|
            anchor = self.anchors_[glyph_name].resolve(name)
 | 
						|
            if anchor is not None and anchor.component == component:
 | 
						|
                raise VoltLibError(
 | 
						|
                    'Anchor "%s" already defined, '
 | 
						|
                    "anchor names are case insensitive" % name,
 | 
						|
                    location,
 | 
						|
                )
 | 
						|
        if self.next_token_ == "LOCKED":
 | 
						|
            locked = True
 | 
						|
            self.advance_lexer_()
 | 
						|
        else:
 | 
						|
            locked = False
 | 
						|
        self.expect_keyword_("AT")
 | 
						|
        pos = self.parse_pos_()
 | 
						|
        self.expect_keyword_("END_ANCHOR")
 | 
						|
        anchor = ast.AnchorDefinition(
 | 
						|
            name, gid, glyph_name, component, locked, pos, location=location
 | 
						|
        )
 | 
						|
        if glyph_name not in self.anchors_:
 | 
						|
            self.anchors_[glyph_name] = SymbolTable()
 | 
						|
        self.anchors_[glyph_name].define(name, anchor)
 | 
						|
        return anchor
 | 
						|
 | 
						|
    def parse_adjust_by_(self):
 | 
						|
        self.advance_lexer_()
 | 
						|
        assert self.is_cur_keyword_("ADJUST_BY")
 | 
						|
        adjustment = self.expect_number_()
 | 
						|
        self.expect_keyword_("AT")
 | 
						|
        size = self.expect_number_()
 | 
						|
        return adjustment, size
 | 
						|
 | 
						|
    def parse_pos_(self):
 | 
						|
        # VOLT syntax doesn't seem to take device Y advance
 | 
						|
        self.advance_lexer_()
 | 
						|
        location = self.cur_token_location_
 | 
						|
        assert self.is_cur_keyword_("POS"), location
 | 
						|
        adv = None
 | 
						|
        dx = None
 | 
						|
        dy = None
 | 
						|
        adv_adjust_by = {}
 | 
						|
        dx_adjust_by = {}
 | 
						|
        dy_adjust_by = {}
 | 
						|
        if self.next_token_ == "ADV":
 | 
						|
            self.advance_lexer_()
 | 
						|
            adv = self.expect_number_()
 | 
						|
            while self.next_token_ == "ADJUST_BY":
 | 
						|
                adjustment, size = self.parse_adjust_by_()
 | 
						|
                adv_adjust_by[size] = adjustment
 | 
						|
        if self.next_token_ == "DX":
 | 
						|
            self.advance_lexer_()
 | 
						|
            dx = self.expect_number_()
 | 
						|
            while self.next_token_ == "ADJUST_BY":
 | 
						|
                adjustment, size = self.parse_adjust_by_()
 | 
						|
                dx_adjust_by[size] = adjustment
 | 
						|
        if self.next_token_ == "DY":
 | 
						|
            self.advance_lexer_()
 | 
						|
            dy = self.expect_number_()
 | 
						|
            while self.next_token_ == "ADJUST_BY":
 | 
						|
                adjustment, size = self.parse_adjust_by_()
 | 
						|
                dy_adjust_by[size] = adjustment
 | 
						|
        self.expect_keyword_("END_POS")
 | 
						|
        return ast.Pos(adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by)
 | 
						|
 | 
						|
    def parse_unicode_values_(self):
 | 
						|
        location = self.cur_token_location_
 | 
						|
        try:
 | 
						|
            unicode_values = self.expect_string_().split(",")
 | 
						|
            unicode_values = [int(uni[2:], 16) for uni in unicode_values if uni != ""]
 | 
						|
        except ValueError as err:
 | 
						|
            raise VoltLibError(str(err), location)
 | 
						|
        return unicode_values if unicode_values != [] else None
 | 
						|
 | 
						|
    def parse_enum_(self):
 | 
						|
        self.expect_keyword_("ENUM")
 | 
						|
        location = self.cur_token_location_
 | 
						|
        enum = ast.Enum(self.parse_coverage_(), location=location)
 | 
						|
        self.expect_keyword_("END_ENUM")
 | 
						|
        return enum
 | 
						|
 | 
						|
    def parse_coverage_(self):
 | 
						|
        coverage = []
 | 
						|
        location = self.cur_token_location_
 | 
						|
        while self.next_token_ in ("GLYPH", "GROUP", "RANGE", "ENUM"):
 | 
						|
            if self.next_token_ == "ENUM":
 | 
						|
                enum = self.parse_enum_()
 | 
						|
                coverage.append(enum)
 | 
						|
            elif self.next_token_ == "GLYPH":
 | 
						|
                self.expect_keyword_("GLYPH")
 | 
						|
                name = self.expect_string_()
 | 
						|
                coverage.append(ast.GlyphName(name, location=location))
 | 
						|
            elif self.next_token_ == "GROUP":
 | 
						|
                self.expect_keyword_("GROUP")
 | 
						|
                name = self.expect_string_()
 | 
						|
                coverage.append(ast.GroupName(name, self, location=location))
 | 
						|
            elif self.next_token_ == "RANGE":
 | 
						|
                self.expect_keyword_("RANGE")
 | 
						|
                start = self.expect_string_()
 | 
						|
                self.expect_keyword_("TO")
 | 
						|
                end = self.expect_string_()
 | 
						|
                coverage.append(ast.Range(start, end, self, location=location))
 | 
						|
        return tuple(coverage)
 | 
						|
 | 
						|
    def resolve_group(self, group_name):
 | 
						|
        return self.groups_.resolve(group_name)
 | 
						|
 | 
						|
    def glyph_range(self, start, end):
 | 
						|
        return self.glyphs_.range(start, end)
 | 
						|
 | 
						|
    def parse_ppem_(self):
 | 
						|
        location = self.cur_token_location_
 | 
						|
        ppem_name = self.cur_token_
 | 
						|
        value = self.expect_number_()
 | 
						|
        setting = ast.SettingDefinition(ppem_name, value, location=location)
 | 
						|
        return setting
 | 
						|
 | 
						|
    def parse_noarg_option_(self):
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = self.cur_token_
 | 
						|
        value = True
 | 
						|
        setting = ast.SettingDefinition(name, value, location=location)
 | 
						|
        return setting
 | 
						|
 | 
						|
    def parse_cmap_format(self):
 | 
						|
        location = self.cur_token_location_
 | 
						|
        name = self.cur_token_
 | 
						|
        value = (self.expect_number_(), self.expect_number_(), self.expect_number_())
 | 
						|
        setting = ast.SettingDefinition(name, value, location=location)
 | 
						|
        return setting
 | 
						|
 | 
						|
    def is_cur_keyword_(self, k):
 | 
						|
        return (self.cur_token_type_ is Lexer.NAME) and (self.cur_token_ == k)
 | 
						|
 | 
						|
    def expect_string_(self):
 | 
						|
        self.advance_lexer_()
 | 
						|
        if self.cur_token_type_ is not Lexer.STRING:
 | 
						|
            raise VoltLibError("Expected a string", self.cur_token_location_)
 | 
						|
        return self.cur_token_
 | 
						|
 | 
						|
    def expect_keyword_(self, keyword):
 | 
						|
        self.advance_lexer_()
 | 
						|
        if self.cur_token_type_ is Lexer.NAME and self.cur_token_ == keyword:
 | 
						|
            return self.cur_token_
 | 
						|
        raise VoltLibError('Expected "%s"' % keyword, self.cur_token_location_)
 | 
						|
 | 
						|
    def expect_name_(self):
 | 
						|
        self.advance_lexer_()
 | 
						|
        if self.cur_token_type_ is Lexer.NAME:
 | 
						|
            return self.cur_token_
 | 
						|
        raise VoltLibError("Expected a name", self.cur_token_location_)
 | 
						|
 | 
						|
    def expect_number_(self):
 | 
						|
        self.advance_lexer_()
 | 
						|
        if self.cur_token_type_ is not Lexer.NUMBER:
 | 
						|
            raise VoltLibError("Expected a number", self.cur_token_location_)
 | 
						|
        return self.cur_token_
 | 
						|
 | 
						|
    def advance_lexer_(self):
 | 
						|
        self.cur_token_type_, self.cur_token_, self.cur_token_location_ = (
 | 
						|
            self.next_token_type_,
 | 
						|
            self.next_token_,
 | 
						|
            self.next_token_location_,
 | 
						|
        )
 | 
						|
        try:
 | 
						|
            if self.is_cur_keyword_("END"):
 | 
						|
                raise StopIteration
 | 
						|
            (
 | 
						|
                self.next_token_type_,
 | 
						|
                self.next_token_,
 | 
						|
                self.next_token_location_,
 | 
						|
            ) = self.lexer_.next()
 | 
						|
        except StopIteration:
 | 
						|
            self.next_token_type_, self.next_token_ = (None, None)
 | 
						|
 | 
						|
 | 
						|
class SymbolTable(object):
 | 
						|
    def __init__(self):
 | 
						|
        self.scopes_ = [{}]
 | 
						|
 | 
						|
    def enter_scope(self):
 | 
						|
        self.scopes_.append({})
 | 
						|
 | 
						|
    def exit_scope(self):
 | 
						|
        self.scopes_.pop()
 | 
						|
 | 
						|
    def define(self, name, item):
 | 
						|
        self.scopes_[-1][name] = item
 | 
						|
 | 
						|
    def resolve(self, name, case_insensitive=True):
 | 
						|
        for scope in reversed(self.scopes_):
 | 
						|
            item = scope.get(name)
 | 
						|
            if item:
 | 
						|
                return item
 | 
						|
        if case_insensitive:
 | 
						|
            for key in scope:
 | 
						|
                if key.lower() == name.lower():
 | 
						|
                    return scope[key]
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class OrderedSymbolTable(SymbolTable):
 | 
						|
    def __init__(self):
 | 
						|
        self.scopes_ = [{}]
 | 
						|
 | 
						|
    def enter_scope(self):
 | 
						|
        self.scopes_.append({})
 | 
						|
 | 
						|
    def resolve(self, name, case_insensitive=False):
 | 
						|
        SymbolTable.resolve(self, name, case_insensitive=case_insensitive)
 | 
						|
 | 
						|
    def range(self, start, end):
 | 
						|
        for scope in reversed(self.scopes_):
 | 
						|
            if start in scope and end in scope:
 | 
						|
                start_idx = list(scope.keys()).index(start)
 | 
						|
                end_idx = list(scope.keys()).index(end)
 | 
						|
                return list(scope.keys())[start_idx : end_idx + 1]
 | 
						|
        return None
 |