1*e1fe3e4aSElliott Hughesimport fontTools.voltLib.ast as ast 2*e1fe3e4aSElliott Hughesfrom fontTools.voltLib.lexer import Lexer 3*e1fe3e4aSElliott Hughesfrom fontTools.voltLib.error import VoltLibError 4*e1fe3e4aSElliott Hughesfrom io import open 5*e1fe3e4aSElliott Hughes 6*e1fe3e4aSElliott HughesPARSE_FUNCS = { 7*e1fe3e4aSElliott Hughes "DEF_GLYPH": "parse_def_glyph_", 8*e1fe3e4aSElliott Hughes "DEF_GROUP": "parse_def_group_", 9*e1fe3e4aSElliott Hughes "DEF_SCRIPT": "parse_def_script_", 10*e1fe3e4aSElliott Hughes "DEF_LOOKUP": "parse_def_lookup_", 11*e1fe3e4aSElliott Hughes "DEF_ANCHOR": "parse_def_anchor_", 12*e1fe3e4aSElliott Hughes "GRID_PPEM": "parse_ppem_", 13*e1fe3e4aSElliott Hughes "PRESENTATION_PPEM": "parse_ppem_", 14*e1fe3e4aSElliott Hughes "PPOSITIONING_PPEM": "parse_ppem_", 15*e1fe3e4aSElliott Hughes "COMPILER_USEEXTENSIONLOOKUPS": "parse_noarg_option_", 16*e1fe3e4aSElliott Hughes "COMPILER_USEPAIRPOSFORMAT2": "parse_noarg_option_", 17*e1fe3e4aSElliott Hughes "CMAP_FORMAT": "parse_cmap_format", 18*e1fe3e4aSElliott Hughes "DO_NOT_TOUCH_CMAP": "parse_noarg_option_", 19*e1fe3e4aSElliott Hughes} 20*e1fe3e4aSElliott Hughes 21*e1fe3e4aSElliott Hughes 22*e1fe3e4aSElliott Hughesclass Parser(object): 23*e1fe3e4aSElliott Hughes def __init__(self, path): 24*e1fe3e4aSElliott Hughes self.doc_ = ast.VoltFile() 25*e1fe3e4aSElliott Hughes self.glyphs_ = OrderedSymbolTable() 26*e1fe3e4aSElliott Hughes self.groups_ = SymbolTable() 27*e1fe3e4aSElliott Hughes self.anchors_ = {} # dictionary of SymbolTable() keyed by glyph 28*e1fe3e4aSElliott Hughes self.scripts_ = SymbolTable() 29*e1fe3e4aSElliott Hughes self.langs_ = SymbolTable() 30*e1fe3e4aSElliott Hughes self.lookups_ = SymbolTable() 31*e1fe3e4aSElliott Hughes self.next_token_type_, self.next_token_ = (None, None) 32*e1fe3e4aSElliott Hughes self.next_token_location_ = None 33*e1fe3e4aSElliott Hughes self.make_lexer_(path) 34*e1fe3e4aSElliott Hughes self.advance_lexer_() 35*e1fe3e4aSElliott Hughes 36*e1fe3e4aSElliott Hughes def make_lexer_(self, file_or_path): 37*e1fe3e4aSElliott Hughes if hasattr(file_or_path, "read"): 38*e1fe3e4aSElliott Hughes filename = getattr(file_or_path, "name", None) 39*e1fe3e4aSElliott Hughes data = file_or_path.read() 40*e1fe3e4aSElliott Hughes else: 41*e1fe3e4aSElliott Hughes filename = file_or_path 42*e1fe3e4aSElliott Hughes with open(file_or_path, "r") as f: 43*e1fe3e4aSElliott Hughes data = f.read() 44*e1fe3e4aSElliott Hughes self.lexer_ = Lexer(data, filename) 45*e1fe3e4aSElliott Hughes 46*e1fe3e4aSElliott Hughes def parse(self): 47*e1fe3e4aSElliott Hughes statements = self.doc_.statements 48*e1fe3e4aSElliott Hughes while self.next_token_type_ is not None: 49*e1fe3e4aSElliott Hughes self.advance_lexer_() 50*e1fe3e4aSElliott Hughes if self.cur_token_ in PARSE_FUNCS.keys(): 51*e1fe3e4aSElliott Hughes func = getattr(self, PARSE_FUNCS[self.cur_token_]) 52*e1fe3e4aSElliott Hughes statements.append(func()) 53*e1fe3e4aSElliott Hughes elif self.is_cur_keyword_("END"): 54*e1fe3e4aSElliott Hughes break 55*e1fe3e4aSElliott Hughes else: 56*e1fe3e4aSElliott Hughes raise VoltLibError( 57*e1fe3e4aSElliott Hughes "Expected " + ", ".join(sorted(PARSE_FUNCS.keys())), 58*e1fe3e4aSElliott Hughes self.cur_token_location_, 59*e1fe3e4aSElliott Hughes ) 60*e1fe3e4aSElliott Hughes return self.doc_ 61*e1fe3e4aSElliott Hughes 62*e1fe3e4aSElliott Hughes def parse_def_glyph_(self): 63*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_GLYPH") 64*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 65*e1fe3e4aSElliott Hughes name = self.expect_string_() 66*e1fe3e4aSElliott Hughes self.expect_keyword_("ID") 67*e1fe3e4aSElliott Hughes gid = self.expect_number_() 68*e1fe3e4aSElliott Hughes if gid < 0: 69*e1fe3e4aSElliott Hughes raise VoltLibError("Invalid glyph ID", self.cur_token_location_) 70*e1fe3e4aSElliott Hughes gunicode = None 71*e1fe3e4aSElliott Hughes if self.next_token_ == "UNICODE": 72*e1fe3e4aSElliott Hughes self.expect_keyword_("UNICODE") 73*e1fe3e4aSElliott Hughes gunicode = [self.expect_number_()] 74*e1fe3e4aSElliott Hughes if gunicode[0] < 0: 75*e1fe3e4aSElliott Hughes raise VoltLibError("Invalid glyph UNICODE", self.cur_token_location_) 76*e1fe3e4aSElliott Hughes elif self.next_token_ == "UNICODEVALUES": 77*e1fe3e4aSElliott Hughes self.expect_keyword_("UNICODEVALUES") 78*e1fe3e4aSElliott Hughes gunicode = self.parse_unicode_values_() 79*e1fe3e4aSElliott Hughes gtype = None 80*e1fe3e4aSElliott Hughes if self.next_token_ == "TYPE": 81*e1fe3e4aSElliott Hughes self.expect_keyword_("TYPE") 82*e1fe3e4aSElliott Hughes gtype = self.expect_name_() 83*e1fe3e4aSElliott Hughes assert gtype in ("BASE", "LIGATURE", "MARK", "COMPONENT") 84*e1fe3e4aSElliott Hughes components = None 85*e1fe3e4aSElliott Hughes if self.next_token_ == "COMPONENTS": 86*e1fe3e4aSElliott Hughes self.expect_keyword_("COMPONENTS") 87*e1fe3e4aSElliott Hughes components = self.expect_number_() 88*e1fe3e4aSElliott Hughes self.expect_keyword_("END_GLYPH") 89*e1fe3e4aSElliott Hughes if self.glyphs_.resolve(name) is not None: 90*e1fe3e4aSElliott Hughes raise VoltLibError( 91*e1fe3e4aSElliott Hughes 'Glyph "%s" (gid %i) already defined' % (name, gid), location 92*e1fe3e4aSElliott Hughes ) 93*e1fe3e4aSElliott Hughes def_glyph = ast.GlyphDefinition( 94*e1fe3e4aSElliott Hughes name, gid, gunicode, gtype, components, location=location 95*e1fe3e4aSElliott Hughes ) 96*e1fe3e4aSElliott Hughes self.glyphs_.define(name, def_glyph) 97*e1fe3e4aSElliott Hughes return def_glyph 98*e1fe3e4aSElliott Hughes 99*e1fe3e4aSElliott Hughes def parse_def_group_(self): 100*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_GROUP") 101*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 102*e1fe3e4aSElliott Hughes name = self.expect_string_() 103*e1fe3e4aSElliott Hughes enum = None 104*e1fe3e4aSElliott Hughes if self.next_token_ == "ENUM": 105*e1fe3e4aSElliott Hughes enum = self.parse_enum_() 106*e1fe3e4aSElliott Hughes self.expect_keyword_("END_GROUP") 107*e1fe3e4aSElliott Hughes if self.groups_.resolve(name) is not None: 108*e1fe3e4aSElliott Hughes raise VoltLibError( 109*e1fe3e4aSElliott Hughes 'Glyph group "%s" already defined, ' 110*e1fe3e4aSElliott Hughes "group names are case insensitive" % name, 111*e1fe3e4aSElliott Hughes location, 112*e1fe3e4aSElliott Hughes ) 113*e1fe3e4aSElliott Hughes def_group = ast.GroupDefinition(name, enum, location=location) 114*e1fe3e4aSElliott Hughes self.groups_.define(name, def_group) 115*e1fe3e4aSElliott Hughes return def_group 116*e1fe3e4aSElliott Hughes 117*e1fe3e4aSElliott Hughes def parse_def_script_(self): 118*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_SCRIPT") 119*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 120*e1fe3e4aSElliott Hughes name = None 121*e1fe3e4aSElliott Hughes if self.next_token_ == "NAME": 122*e1fe3e4aSElliott Hughes self.expect_keyword_("NAME") 123*e1fe3e4aSElliott Hughes name = self.expect_string_() 124*e1fe3e4aSElliott Hughes self.expect_keyword_("TAG") 125*e1fe3e4aSElliott Hughes tag = self.expect_string_() 126*e1fe3e4aSElliott Hughes if self.scripts_.resolve(tag) is not None: 127*e1fe3e4aSElliott Hughes raise VoltLibError( 128*e1fe3e4aSElliott Hughes 'Script "%s" already defined, ' 129*e1fe3e4aSElliott Hughes "script tags are case insensitive" % tag, 130*e1fe3e4aSElliott Hughes location, 131*e1fe3e4aSElliott Hughes ) 132*e1fe3e4aSElliott Hughes self.langs_.enter_scope() 133*e1fe3e4aSElliott Hughes langs = [] 134*e1fe3e4aSElliott Hughes while self.next_token_ != "END_SCRIPT": 135*e1fe3e4aSElliott Hughes self.advance_lexer_() 136*e1fe3e4aSElliott Hughes lang = self.parse_langsys_() 137*e1fe3e4aSElliott Hughes self.expect_keyword_("END_LANGSYS") 138*e1fe3e4aSElliott Hughes if self.langs_.resolve(lang.tag) is not None: 139*e1fe3e4aSElliott Hughes raise VoltLibError( 140*e1fe3e4aSElliott Hughes 'Language "%s" already defined in script "%s", ' 141*e1fe3e4aSElliott Hughes "language tags are case insensitive" % (lang.tag, tag), 142*e1fe3e4aSElliott Hughes location, 143*e1fe3e4aSElliott Hughes ) 144*e1fe3e4aSElliott Hughes self.langs_.define(lang.tag, lang) 145*e1fe3e4aSElliott Hughes langs.append(lang) 146*e1fe3e4aSElliott Hughes self.expect_keyword_("END_SCRIPT") 147*e1fe3e4aSElliott Hughes self.langs_.exit_scope() 148*e1fe3e4aSElliott Hughes def_script = ast.ScriptDefinition(name, tag, langs, location=location) 149*e1fe3e4aSElliott Hughes self.scripts_.define(tag, def_script) 150*e1fe3e4aSElliott Hughes return def_script 151*e1fe3e4aSElliott Hughes 152*e1fe3e4aSElliott Hughes def parse_langsys_(self): 153*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_LANGSYS") 154*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 155*e1fe3e4aSElliott Hughes name = None 156*e1fe3e4aSElliott Hughes if self.next_token_ == "NAME": 157*e1fe3e4aSElliott Hughes self.expect_keyword_("NAME") 158*e1fe3e4aSElliott Hughes name = self.expect_string_() 159*e1fe3e4aSElliott Hughes self.expect_keyword_("TAG") 160*e1fe3e4aSElliott Hughes tag = self.expect_string_() 161*e1fe3e4aSElliott Hughes features = [] 162*e1fe3e4aSElliott Hughes while self.next_token_ != "END_LANGSYS": 163*e1fe3e4aSElliott Hughes self.advance_lexer_() 164*e1fe3e4aSElliott Hughes feature = self.parse_feature_() 165*e1fe3e4aSElliott Hughes self.expect_keyword_("END_FEATURE") 166*e1fe3e4aSElliott Hughes features.append(feature) 167*e1fe3e4aSElliott Hughes def_langsys = ast.LangSysDefinition(name, tag, features, location=location) 168*e1fe3e4aSElliott Hughes return def_langsys 169*e1fe3e4aSElliott Hughes 170*e1fe3e4aSElliott Hughes def parse_feature_(self): 171*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_FEATURE") 172*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 173*e1fe3e4aSElliott Hughes self.expect_keyword_("NAME") 174*e1fe3e4aSElliott Hughes name = self.expect_string_() 175*e1fe3e4aSElliott Hughes self.expect_keyword_("TAG") 176*e1fe3e4aSElliott Hughes tag = self.expect_string_() 177*e1fe3e4aSElliott Hughes lookups = [] 178*e1fe3e4aSElliott Hughes while self.next_token_ != "END_FEATURE": 179*e1fe3e4aSElliott Hughes # self.advance_lexer_() 180*e1fe3e4aSElliott Hughes self.expect_keyword_("LOOKUP") 181*e1fe3e4aSElliott Hughes lookup = self.expect_string_() 182*e1fe3e4aSElliott Hughes lookups.append(lookup) 183*e1fe3e4aSElliott Hughes feature = ast.FeatureDefinition(name, tag, lookups, location=location) 184*e1fe3e4aSElliott Hughes return feature 185*e1fe3e4aSElliott Hughes 186*e1fe3e4aSElliott Hughes def parse_def_lookup_(self): 187*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_LOOKUP") 188*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 189*e1fe3e4aSElliott Hughes name = self.expect_string_() 190*e1fe3e4aSElliott Hughes if not name[0].isalpha(): 191*e1fe3e4aSElliott Hughes raise VoltLibError( 192*e1fe3e4aSElliott Hughes 'Lookup name "%s" must start with a letter' % name, location 193*e1fe3e4aSElliott Hughes ) 194*e1fe3e4aSElliott Hughes if self.lookups_.resolve(name) is not None: 195*e1fe3e4aSElliott Hughes raise VoltLibError( 196*e1fe3e4aSElliott Hughes 'Lookup "%s" already defined, ' 197*e1fe3e4aSElliott Hughes "lookup names are case insensitive" % name, 198*e1fe3e4aSElliott Hughes location, 199*e1fe3e4aSElliott Hughes ) 200*e1fe3e4aSElliott Hughes process_base = True 201*e1fe3e4aSElliott Hughes if self.next_token_ == "PROCESS_BASE": 202*e1fe3e4aSElliott Hughes self.advance_lexer_() 203*e1fe3e4aSElliott Hughes elif self.next_token_ == "SKIP_BASE": 204*e1fe3e4aSElliott Hughes self.advance_lexer_() 205*e1fe3e4aSElliott Hughes process_base = False 206*e1fe3e4aSElliott Hughes process_marks = True 207*e1fe3e4aSElliott Hughes mark_glyph_set = None 208*e1fe3e4aSElliott Hughes if self.next_token_ == "PROCESS_MARKS": 209*e1fe3e4aSElliott Hughes self.advance_lexer_() 210*e1fe3e4aSElliott Hughes if self.next_token_ == "MARK_GLYPH_SET": 211*e1fe3e4aSElliott Hughes self.advance_lexer_() 212*e1fe3e4aSElliott Hughes mark_glyph_set = self.expect_string_() 213*e1fe3e4aSElliott Hughes elif self.next_token_ == "ALL": 214*e1fe3e4aSElliott Hughes self.advance_lexer_() 215*e1fe3e4aSElliott Hughes elif self.next_token_ == "NONE": 216*e1fe3e4aSElliott Hughes self.advance_lexer_() 217*e1fe3e4aSElliott Hughes process_marks = False 218*e1fe3e4aSElliott Hughes elif self.next_token_type_ == Lexer.STRING: 219*e1fe3e4aSElliott Hughes process_marks = self.expect_string_() 220*e1fe3e4aSElliott Hughes else: 221*e1fe3e4aSElliott Hughes raise VoltLibError( 222*e1fe3e4aSElliott Hughes "Expected ALL, NONE, MARK_GLYPH_SET or an ID. " 223*e1fe3e4aSElliott Hughes "Got %s" % (self.next_token_type_), 224*e1fe3e4aSElliott Hughes location, 225*e1fe3e4aSElliott Hughes ) 226*e1fe3e4aSElliott Hughes elif self.next_token_ == "SKIP_MARKS": 227*e1fe3e4aSElliott Hughes self.advance_lexer_() 228*e1fe3e4aSElliott Hughes process_marks = False 229*e1fe3e4aSElliott Hughes direction = None 230*e1fe3e4aSElliott Hughes if self.next_token_ == "DIRECTION": 231*e1fe3e4aSElliott Hughes self.expect_keyword_("DIRECTION") 232*e1fe3e4aSElliott Hughes direction = self.expect_name_() 233*e1fe3e4aSElliott Hughes assert direction in ("LTR", "RTL") 234*e1fe3e4aSElliott Hughes reversal = None 235*e1fe3e4aSElliott Hughes if self.next_token_ == "REVERSAL": 236*e1fe3e4aSElliott Hughes self.expect_keyword_("REVERSAL") 237*e1fe3e4aSElliott Hughes reversal = True 238*e1fe3e4aSElliott Hughes comments = None 239*e1fe3e4aSElliott Hughes if self.next_token_ == "COMMENTS": 240*e1fe3e4aSElliott Hughes self.expect_keyword_("COMMENTS") 241*e1fe3e4aSElliott Hughes comments = self.expect_string_().replace(r"\n", "\n") 242*e1fe3e4aSElliott Hughes context = [] 243*e1fe3e4aSElliott Hughes while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"): 244*e1fe3e4aSElliott Hughes context = self.parse_context_() 245*e1fe3e4aSElliott Hughes as_pos_or_sub = self.expect_name_() 246*e1fe3e4aSElliott Hughes sub = None 247*e1fe3e4aSElliott Hughes pos = None 248*e1fe3e4aSElliott Hughes if as_pos_or_sub == "AS_SUBSTITUTION": 249*e1fe3e4aSElliott Hughes sub = self.parse_substitution_(reversal) 250*e1fe3e4aSElliott Hughes elif as_pos_or_sub == "AS_POSITION": 251*e1fe3e4aSElliott Hughes pos = self.parse_position_() 252*e1fe3e4aSElliott Hughes else: 253*e1fe3e4aSElliott Hughes raise VoltLibError( 254*e1fe3e4aSElliott Hughes "Expected AS_SUBSTITUTION or AS_POSITION. " "Got %s" % (as_pos_or_sub), 255*e1fe3e4aSElliott Hughes location, 256*e1fe3e4aSElliott Hughes ) 257*e1fe3e4aSElliott Hughes def_lookup = ast.LookupDefinition( 258*e1fe3e4aSElliott Hughes name, 259*e1fe3e4aSElliott Hughes process_base, 260*e1fe3e4aSElliott Hughes process_marks, 261*e1fe3e4aSElliott Hughes mark_glyph_set, 262*e1fe3e4aSElliott Hughes direction, 263*e1fe3e4aSElliott Hughes reversal, 264*e1fe3e4aSElliott Hughes comments, 265*e1fe3e4aSElliott Hughes context, 266*e1fe3e4aSElliott Hughes sub, 267*e1fe3e4aSElliott Hughes pos, 268*e1fe3e4aSElliott Hughes location=location, 269*e1fe3e4aSElliott Hughes ) 270*e1fe3e4aSElliott Hughes self.lookups_.define(name, def_lookup) 271*e1fe3e4aSElliott Hughes return def_lookup 272*e1fe3e4aSElliott Hughes 273*e1fe3e4aSElliott Hughes def parse_context_(self): 274*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 275*e1fe3e4aSElliott Hughes contexts = [] 276*e1fe3e4aSElliott Hughes while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"): 277*e1fe3e4aSElliott Hughes side = None 278*e1fe3e4aSElliott Hughes coverage = None 279*e1fe3e4aSElliott Hughes ex_or_in = self.expect_name_() 280*e1fe3e4aSElliott Hughes # side_contexts = [] # XXX 281*e1fe3e4aSElliott Hughes if self.next_token_ != "END_CONTEXT": 282*e1fe3e4aSElliott Hughes left = [] 283*e1fe3e4aSElliott Hughes right = [] 284*e1fe3e4aSElliott Hughes while self.next_token_ in ("LEFT", "RIGHT"): 285*e1fe3e4aSElliott Hughes side = self.expect_name_() 286*e1fe3e4aSElliott Hughes coverage = self.parse_coverage_() 287*e1fe3e4aSElliott Hughes if side == "LEFT": 288*e1fe3e4aSElliott Hughes left.append(coverage) 289*e1fe3e4aSElliott Hughes else: 290*e1fe3e4aSElliott Hughes right.append(coverage) 291*e1fe3e4aSElliott Hughes self.expect_keyword_("END_CONTEXT") 292*e1fe3e4aSElliott Hughes context = ast.ContextDefinition( 293*e1fe3e4aSElliott Hughes ex_or_in, left, right, location=location 294*e1fe3e4aSElliott Hughes ) 295*e1fe3e4aSElliott Hughes contexts.append(context) 296*e1fe3e4aSElliott Hughes else: 297*e1fe3e4aSElliott Hughes self.expect_keyword_("END_CONTEXT") 298*e1fe3e4aSElliott Hughes return contexts 299*e1fe3e4aSElliott Hughes 300*e1fe3e4aSElliott Hughes def parse_substitution_(self, reversal): 301*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("AS_SUBSTITUTION") 302*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 303*e1fe3e4aSElliott Hughes src = [] 304*e1fe3e4aSElliott Hughes dest = [] 305*e1fe3e4aSElliott Hughes if self.next_token_ != "SUB": 306*e1fe3e4aSElliott Hughes raise VoltLibError("Expected SUB", location) 307*e1fe3e4aSElliott Hughes while self.next_token_ == "SUB": 308*e1fe3e4aSElliott Hughes self.expect_keyword_("SUB") 309*e1fe3e4aSElliott Hughes src.append(self.parse_coverage_()) 310*e1fe3e4aSElliott Hughes self.expect_keyword_("WITH") 311*e1fe3e4aSElliott Hughes dest.append(self.parse_coverage_()) 312*e1fe3e4aSElliott Hughes self.expect_keyword_("END_SUB") 313*e1fe3e4aSElliott Hughes self.expect_keyword_("END_SUBSTITUTION") 314*e1fe3e4aSElliott Hughes max_src = max([len(cov) for cov in src]) 315*e1fe3e4aSElliott Hughes max_dest = max([len(cov) for cov in dest]) 316*e1fe3e4aSElliott Hughes # many to many or mixed is invalid 317*e1fe3e4aSElliott Hughes if (max_src > 1 and max_dest > 1) or ( 318*e1fe3e4aSElliott Hughes reversal and (max_src > 1 or max_dest > 1) 319*e1fe3e4aSElliott Hughes ): 320*e1fe3e4aSElliott Hughes raise VoltLibError("Invalid substitution type", location) 321*e1fe3e4aSElliott Hughes mapping = dict(zip(tuple(src), tuple(dest))) 322*e1fe3e4aSElliott Hughes if max_src == 1 and max_dest == 1: 323*e1fe3e4aSElliott Hughes if reversal: 324*e1fe3e4aSElliott Hughes sub = ast.SubstitutionReverseChainingSingleDefinition( 325*e1fe3e4aSElliott Hughes mapping, location=location 326*e1fe3e4aSElliott Hughes ) 327*e1fe3e4aSElliott Hughes else: 328*e1fe3e4aSElliott Hughes sub = ast.SubstitutionSingleDefinition(mapping, location=location) 329*e1fe3e4aSElliott Hughes elif max_src == 1 and max_dest > 1: 330*e1fe3e4aSElliott Hughes sub = ast.SubstitutionMultipleDefinition(mapping, location=location) 331*e1fe3e4aSElliott Hughes elif max_src > 1 and max_dest == 1: 332*e1fe3e4aSElliott Hughes sub = ast.SubstitutionLigatureDefinition(mapping, location=location) 333*e1fe3e4aSElliott Hughes return sub 334*e1fe3e4aSElliott Hughes 335*e1fe3e4aSElliott Hughes def parse_position_(self): 336*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("AS_POSITION") 337*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 338*e1fe3e4aSElliott Hughes pos_type = self.expect_name_() 339*e1fe3e4aSElliott Hughes if pos_type not in ("ATTACH", "ATTACH_CURSIVE", "ADJUST_PAIR", "ADJUST_SINGLE"): 340*e1fe3e4aSElliott Hughes raise VoltLibError( 341*e1fe3e4aSElliott Hughes "Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE", location 342*e1fe3e4aSElliott Hughes ) 343*e1fe3e4aSElliott Hughes if pos_type == "ATTACH": 344*e1fe3e4aSElliott Hughes position = self.parse_attach_() 345*e1fe3e4aSElliott Hughes elif pos_type == "ATTACH_CURSIVE": 346*e1fe3e4aSElliott Hughes position = self.parse_attach_cursive_() 347*e1fe3e4aSElliott Hughes elif pos_type == "ADJUST_PAIR": 348*e1fe3e4aSElliott Hughes position = self.parse_adjust_pair_() 349*e1fe3e4aSElliott Hughes elif pos_type == "ADJUST_SINGLE": 350*e1fe3e4aSElliott Hughes position = self.parse_adjust_single_() 351*e1fe3e4aSElliott Hughes self.expect_keyword_("END_POSITION") 352*e1fe3e4aSElliott Hughes return position 353*e1fe3e4aSElliott Hughes 354*e1fe3e4aSElliott Hughes def parse_attach_(self): 355*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("ATTACH") 356*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 357*e1fe3e4aSElliott Hughes coverage = self.parse_coverage_() 358*e1fe3e4aSElliott Hughes coverage_to = [] 359*e1fe3e4aSElliott Hughes self.expect_keyword_("TO") 360*e1fe3e4aSElliott Hughes while self.next_token_ != "END_ATTACH": 361*e1fe3e4aSElliott Hughes cov = self.parse_coverage_() 362*e1fe3e4aSElliott Hughes self.expect_keyword_("AT") 363*e1fe3e4aSElliott Hughes self.expect_keyword_("ANCHOR") 364*e1fe3e4aSElliott Hughes anchor_name = self.expect_string_() 365*e1fe3e4aSElliott Hughes coverage_to.append((cov, anchor_name)) 366*e1fe3e4aSElliott Hughes self.expect_keyword_("END_ATTACH") 367*e1fe3e4aSElliott Hughes position = ast.PositionAttachDefinition( 368*e1fe3e4aSElliott Hughes coverage, coverage_to, location=location 369*e1fe3e4aSElliott Hughes ) 370*e1fe3e4aSElliott Hughes return position 371*e1fe3e4aSElliott Hughes 372*e1fe3e4aSElliott Hughes def parse_attach_cursive_(self): 373*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("ATTACH_CURSIVE") 374*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 375*e1fe3e4aSElliott Hughes coverages_exit = [] 376*e1fe3e4aSElliott Hughes coverages_enter = [] 377*e1fe3e4aSElliott Hughes while self.next_token_ != "ENTER": 378*e1fe3e4aSElliott Hughes self.expect_keyword_("EXIT") 379*e1fe3e4aSElliott Hughes coverages_exit.append(self.parse_coverage_()) 380*e1fe3e4aSElliott Hughes while self.next_token_ != "END_ATTACH": 381*e1fe3e4aSElliott Hughes self.expect_keyword_("ENTER") 382*e1fe3e4aSElliott Hughes coverages_enter.append(self.parse_coverage_()) 383*e1fe3e4aSElliott Hughes self.expect_keyword_("END_ATTACH") 384*e1fe3e4aSElliott Hughes position = ast.PositionAttachCursiveDefinition( 385*e1fe3e4aSElliott Hughes coverages_exit, coverages_enter, location=location 386*e1fe3e4aSElliott Hughes ) 387*e1fe3e4aSElliott Hughes return position 388*e1fe3e4aSElliott Hughes 389*e1fe3e4aSElliott Hughes def parse_adjust_pair_(self): 390*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("ADJUST_PAIR") 391*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 392*e1fe3e4aSElliott Hughes coverages_1 = [] 393*e1fe3e4aSElliott Hughes coverages_2 = [] 394*e1fe3e4aSElliott Hughes adjust_pair = {} 395*e1fe3e4aSElliott Hughes while self.next_token_ == "FIRST": 396*e1fe3e4aSElliott Hughes self.advance_lexer_() 397*e1fe3e4aSElliott Hughes coverage_1 = self.parse_coverage_() 398*e1fe3e4aSElliott Hughes coverages_1.append(coverage_1) 399*e1fe3e4aSElliott Hughes while self.next_token_ == "SECOND": 400*e1fe3e4aSElliott Hughes self.advance_lexer_() 401*e1fe3e4aSElliott Hughes coverage_2 = self.parse_coverage_() 402*e1fe3e4aSElliott Hughes coverages_2.append(coverage_2) 403*e1fe3e4aSElliott Hughes while self.next_token_ != "END_ADJUST": 404*e1fe3e4aSElliott Hughes id_1 = self.expect_number_() 405*e1fe3e4aSElliott Hughes id_2 = self.expect_number_() 406*e1fe3e4aSElliott Hughes self.expect_keyword_("BY") 407*e1fe3e4aSElliott Hughes pos_1 = self.parse_pos_() 408*e1fe3e4aSElliott Hughes pos_2 = self.parse_pos_() 409*e1fe3e4aSElliott Hughes adjust_pair[(id_1, id_2)] = (pos_1, pos_2) 410*e1fe3e4aSElliott Hughes self.expect_keyword_("END_ADJUST") 411*e1fe3e4aSElliott Hughes position = ast.PositionAdjustPairDefinition( 412*e1fe3e4aSElliott Hughes coverages_1, coverages_2, adjust_pair, location=location 413*e1fe3e4aSElliott Hughes ) 414*e1fe3e4aSElliott Hughes return position 415*e1fe3e4aSElliott Hughes 416*e1fe3e4aSElliott Hughes def parse_adjust_single_(self): 417*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("ADJUST_SINGLE") 418*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 419*e1fe3e4aSElliott Hughes adjust_single = [] 420*e1fe3e4aSElliott Hughes while self.next_token_ != "END_ADJUST": 421*e1fe3e4aSElliott Hughes coverages = self.parse_coverage_() 422*e1fe3e4aSElliott Hughes self.expect_keyword_("BY") 423*e1fe3e4aSElliott Hughes pos = self.parse_pos_() 424*e1fe3e4aSElliott Hughes adjust_single.append((coverages, pos)) 425*e1fe3e4aSElliott Hughes self.expect_keyword_("END_ADJUST") 426*e1fe3e4aSElliott Hughes position = ast.PositionAdjustSingleDefinition(adjust_single, location=location) 427*e1fe3e4aSElliott Hughes return position 428*e1fe3e4aSElliott Hughes 429*e1fe3e4aSElliott Hughes def parse_def_anchor_(self): 430*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("DEF_ANCHOR") 431*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 432*e1fe3e4aSElliott Hughes name = self.expect_string_() 433*e1fe3e4aSElliott Hughes self.expect_keyword_("ON") 434*e1fe3e4aSElliott Hughes gid = self.expect_number_() 435*e1fe3e4aSElliott Hughes self.expect_keyword_("GLYPH") 436*e1fe3e4aSElliott Hughes glyph_name = self.expect_name_() 437*e1fe3e4aSElliott Hughes self.expect_keyword_("COMPONENT") 438*e1fe3e4aSElliott Hughes component = self.expect_number_() 439*e1fe3e4aSElliott Hughes # check for duplicate anchor names on this glyph 440*e1fe3e4aSElliott Hughes if glyph_name in self.anchors_: 441*e1fe3e4aSElliott Hughes anchor = self.anchors_[glyph_name].resolve(name) 442*e1fe3e4aSElliott Hughes if anchor is not None and anchor.component == component: 443*e1fe3e4aSElliott Hughes raise VoltLibError( 444*e1fe3e4aSElliott Hughes 'Anchor "%s" already defined, ' 445*e1fe3e4aSElliott Hughes "anchor names are case insensitive" % name, 446*e1fe3e4aSElliott Hughes location, 447*e1fe3e4aSElliott Hughes ) 448*e1fe3e4aSElliott Hughes if self.next_token_ == "LOCKED": 449*e1fe3e4aSElliott Hughes locked = True 450*e1fe3e4aSElliott Hughes self.advance_lexer_() 451*e1fe3e4aSElliott Hughes else: 452*e1fe3e4aSElliott Hughes locked = False 453*e1fe3e4aSElliott Hughes self.expect_keyword_("AT") 454*e1fe3e4aSElliott Hughes pos = self.parse_pos_() 455*e1fe3e4aSElliott Hughes self.expect_keyword_("END_ANCHOR") 456*e1fe3e4aSElliott Hughes anchor = ast.AnchorDefinition( 457*e1fe3e4aSElliott Hughes name, gid, glyph_name, component, locked, pos, location=location 458*e1fe3e4aSElliott Hughes ) 459*e1fe3e4aSElliott Hughes if glyph_name not in self.anchors_: 460*e1fe3e4aSElliott Hughes self.anchors_[glyph_name] = SymbolTable() 461*e1fe3e4aSElliott Hughes self.anchors_[glyph_name].define(name, anchor) 462*e1fe3e4aSElliott Hughes return anchor 463*e1fe3e4aSElliott Hughes 464*e1fe3e4aSElliott Hughes def parse_adjust_by_(self): 465*e1fe3e4aSElliott Hughes self.advance_lexer_() 466*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("ADJUST_BY") 467*e1fe3e4aSElliott Hughes adjustment = self.expect_number_() 468*e1fe3e4aSElliott Hughes self.expect_keyword_("AT") 469*e1fe3e4aSElliott Hughes size = self.expect_number_() 470*e1fe3e4aSElliott Hughes return adjustment, size 471*e1fe3e4aSElliott Hughes 472*e1fe3e4aSElliott Hughes def parse_pos_(self): 473*e1fe3e4aSElliott Hughes # VOLT syntax doesn't seem to take device Y advance 474*e1fe3e4aSElliott Hughes self.advance_lexer_() 475*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 476*e1fe3e4aSElliott Hughes assert self.is_cur_keyword_("POS"), location 477*e1fe3e4aSElliott Hughes adv = None 478*e1fe3e4aSElliott Hughes dx = None 479*e1fe3e4aSElliott Hughes dy = None 480*e1fe3e4aSElliott Hughes adv_adjust_by = {} 481*e1fe3e4aSElliott Hughes dx_adjust_by = {} 482*e1fe3e4aSElliott Hughes dy_adjust_by = {} 483*e1fe3e4aSElliott Hughes if self.next_token_ == "ADV": 484*e1fe3e4aSElliott Hughes self.advance_lexer_() 485*e1fe3e4aSElliott Hughes adv = self.expect_number_() 486*e1fe3e4aSElliott Hughes while self.next_token_ == "ADJUST_BY": 487*e1fe3e4aSElliott Hughes adjustment, size = self.parse_adjust_by_() 488*e1fe3e4aSElliott Hughes adv_adjust_by[size] = adjustment 489*e1fe3e4aSElliott Hughes if self.next_token_ == "DX": 490*e1fe3e4aSElliott Hughes self.advance_lexer_() 491*e1fe3e4aSElliott Hughes dx = self.expect_number_() 492*e1fe3e4aSElliott Hughes while self.next_token_ == "ADJUST_BY": 493*e1fe3e4aSElliott Hughes adjustment, size = self.parse_adjust_by_() 494*e1fe3e4aSElliott Hughes dx_adjust_by[size] = adjustment 495*e1fe3e4aSElliott Hughes if self.next_token_ == "DY": 496*e1fe3e4aSElliott Hughes self.advance_lexer_() 497*e1fe3e4aSElliott Hughes dy = self.expect_number_() 498*e1fe3e4aSElliott Hughes while self.next_token_ == "ADJUST_BY": 499*e1fe3e4aSElliott Hughes adjustment, size = self.parse_adjust_by_() 500*e1fe3e4aSElliott Hughes dy_adjust_by[size] = adjustment 501*e1fe3e4aSElliott Hughes self.expect_keyword_("END_POS") 502*e1fe3e4aSElliott Hughes return ast.Pos(adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by) 503*e1fe3e4aSElliott Hughes 504*e1fe3e4aSElliott Hughes def parse_unicode_values_(self): 505*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 506*e1fe3e4aSElliott Hughes try: 507*e1fe3e4aSElliott Hughes unicode_values = self.expect_string_().split(",") 508*e1fe3e4aSElliott Hughes unicode_values = [int(uni[2:], 16) for uni in unicode_values if uni != ""] 509*e1fe3e4aSElliott Hughes except ValueError as err: 510*e1fe3e4aSElliott Hughes raise VoltLibError(str(err), location) 511*e1fe3e4aSElliott Hughes return unicode_values if unicode_values != [] else None 512*e1fe3e4aSElliott Hughes 513*e1fe3e4aSElliott Hughes def parse_enum_(self): 514*e1fe3e4aSElliott Hughes self.expect_keyword_("ENUM") 515*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 516*e1fe3e4aSElliott Hughes enum = ast.Enum(self.parse_coverage_(), location=location) 517*e1fe3e4aSElliott Hughes self.expect_keyword_("END_ENUM") 518*e1fe3e4aSElliott Hughes return enum 519*e1fe3e4aSElliott Hughes 520*e1fe3e4aSElliott Hughes def parse_coverage_(self): 521*e1fe3e4aSElliott Hughes coverage = [] 522*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 523*e1fe3e4aSElliott Hughes while self.next_token_ in ("GLYPH", "GROUP", "RANGE", "ENUM"): 524*e1fe3e4aSElliott Hughes if self.next_token_ == "ENUM": 525*e1fe3e4aSElliott Hughes enum = self.parse_enum_() 526*e1fe3e4aSElliott Hughes coverage.append(enum) 527*e1fe3e4aSElliott Hughes elif self.next_token_ == "GLYPH": 528*e1fe3e4aSElliott Hughes self.expect_keyword_("GLYPH") 529*e1fe3e4aSElliott Hughes name = self.expect_string_() 530*e1fe3e4aSElliott Hughes coverage.append(ast.GlyphName(name, location=location)) 531*e1fe3e4aSElliott Hughes elif self.next_token_ == "GROUP": 532*e1fe3e4aSElliott Hughes self.expect_keyword_("GROUP") 533*e1fe3e4aSElliott Hughes name = self.expect_string_() 534*e1fe3e4aSElliott Hughes coverage.append(ast.GroupName(name, self, location=location)) 535*e1fe3e4aSElliott Hughes elif self.next_token_ == "RANGE": 536*e1fe3e4aSElliott Hughes self.expect_keyword_("RANGE") 537*e1fe3e4aSElliott Hughes start = self.expect_string_() 538*e1fe3e4aSElliott Hughes self.expect_keyword_("TO") 539*e1fe3e4aSElliott Hughes end = self.expect_string_() 540*e1fe3e4aSElliott Hughes coverage.append(ast.Range(start, end, self, location=location)) 541*e1fe3e4aSElliott Hughes return tuple(coverage) 542*e1fe3e4aSElliott Hughes 543*e1fe3e4aSElliott Hughes def resolve_group(self, group_name): 544*e1fe3e4aSElliott Hughes return self.groups_.resolve(group_name) 545*e1fe3e4aSElliott Hughes 546*e1fe3e4aSElliott Hughes def glyph_range(self, start, end): 547*e1fe3e4aSElliott Hughes return self.glyphs_.range(start, end) 548*e1fe3e4aSElliott Hughes 549*e1fe3e4aSElliott Hughes def parse_ppem_(self): 550*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 551*e1fe3e4aSElliott Hughes ppem_name = self.cur_token_ 552*e1fe3e4aSElliott Hughes value = self.expect_number_() 553*e1fe3e4aSElliott Hughes setting = ast.SettingDefinition(ppem_name, value, location=location) 554*e1fe3e4aSElliott Hughes return setting 555*e1fe3e4aSElliott Hughes 556*e1fe3e4aSElliott Hughes def parse_noarg_option_(self): 557*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 558*e1fe3e4aSElliott Hughes name = self.cur_token_ 559*e1fe3e4aSElliott Hughes value = True 560*e1fe3e4aSElliott Hughes setting = ast.SettingDefinition(name, value, location=location) 561*e1fe3e4aSElliott Hughes return setting 562*e1fe3e4aSElliott Hughes 563*e1fe3e4aSElliott Hughes def parse_cmap_format(self): 564*e1fe3e4aSElliott Hughes location = self.cur_token_location_ 565*e1fe3e4aSElliott Hughes name = self.cur_token_ 566*e1fe3e4aSElliott Hughes value = (self.expect_number_(), self.expect_number_(), self.expect_number_()) 567*e1fe3e4aSElliott Hughes setting = ast.SettingDefinition(name, value, location=location) 568*e1fe3e4aSElliott Hughes return setting 569*e1fe3e4aSElliott Hughes 570*e1fe3e4aSElliott Hughes def is_cur_keyword_(self, k): 571*e1fe3e4aSElliott Hughes return (self.cur_token_type_ is Lexer.NAME) and (self.cur_token_ == k) 572*e1fe3e4aSElliott Hughes 573*e1fe3e4aSElliott Hughes def expect_string_(self): 574*e1fe3e4aSElliott Hughes self.advance_lexer_() 575*e1fe3e4aSElliott Hughes if self.cur_token_type_ is not Lexer.STRING: 576*e1fe3e4aSElliott Hughes raise VoltLibError("Expected a string", self.cur_token_location_) 577*e1fe3e4aSElliott Hughes return self.cur_token_ 578*e1fe3e4aSElliott Hughes 579*e1fe3e4aSElliott Hughes def expect_keyword_(self, keyword): 580*e1fe3e4aSElliott Hughes self.advance_lexer_() 581*e1fe3e4aSElliott Hughes if self.cur_token_type_ is Lexer.NAME and self.cur_token_ == keyword: 582*e1fe3e4aSElliott Hughes return self.cur_token_ 583*e1fe3e4aSElliott Hughes raise VoltLibError('Expected "%s"' % keyword, self.cur_token_location_) 584*e1fe3e4aSElliott Hughes 585*e1fe3e4aSElliott Hughes def expect_name_(self): 586*e1fe3e4aSElliott Hughes self.advance_lexer_() 587*e1fe3e4aSElliott Hughes if self.cur_token_type_ is Lexer.NAME: 588*e1fe3e4aSElliott Hughes return self.cur_token_ 589*e1fe3e4aSElliott Hughes raise VoltLibError("Expected a name", self.cur_token_location_) 590*e1fe3e4aSElliott Hughes 591*e1fe3e4aSElliott Hughes def expect_number_(self): 592*e1fe3e4aSElliott Hughes self.advance_lexer_() 593*e1fe3e4aSElliott Hughes if self.cur_token_type_ is not Lexer.NUMBER: 594*e1fe3e4aSElliott Hughes raise VoltLibError("Expected a number", self.cur_token_location_) 595*e1fe3e4aSElliott Hughes return self.cur_token_ 596*e1fe3e4aSElliott Hughes 597*e1fe3e4aSElliott Hughes def advance_lexer_(self): 598*e1fe3e4aSElliott Hughes self.cur_token_type_, self.cur_token_, self.cur_token_location_ = ( 599*e1fe3e4aSElliott Hughes self.next_token_type_, 600*e1fe3e4aSElliott Hughes self.next_token_, 601*e1fe3e4aSElliott Hughes self.next_token_location_, 602*e1fe3e4aSElliott Hughes ) 603*e1fe3e4aSElliott Hughes try: 604*e1fe3e4aSElliott Hughes if self.is_cur_keyword_("END"): 605*e1fe3e4aSElliott Hughes raise StopIteration 606*e1fe3e4aSElliott Hughes ( 607*e1fe3e4aSElliott Hughes self.next_token_type_, 608*e1fe3e4aSElliott Hughes self.next_token_, 609*e1fe3e4aSElliott Hughes self.next_token_location_, 610*e1fe3e4aSElliott Hughes ) = self.lexer_.next() 611*e1fe3e4aSElliott Hughes except StopIteration: 612*e1fe3e4aSElliott Hughes self.next_token_type_, self.next_token_ = (None, None) 613*e1fe3e4aSElliott Hughes 614*e1fe3e4aSElliott Hughes 615*e1fe3e4aSElliott Hughesclass SymbolTable(object): 616*e1fe3e4aSElliott Hughes def __init__(self): 617*e1fe3e4aSElliott Hughes self.scopes_ = [{}] 618*e1fe3e4aSElliott Hughes 619*e1fe3e4aSElliott Hughes def enter_scope(self): 620*e1fe3e4aSElliott Hughes self.scopes_.append({}) 621*e1fe3e4aSElliott Hughes 622*e1fe3e4aSElliott Hughes def exit_scope(self): 623*e1fe3e4aSElliott Hughes self.scopes_.pop() 624*e1fe3e4aSElliott Hughes 625*e1fe3e4aSElliott Hughes def define(self, name, item): 626*e1fe3e4aSElliott Hughes self.scopes_[-1][name] = item 627*e1fe3e4aSElliott Hughes 628*e1fe3e4aSElliott Hughes def resolve(self, name, case_insensitive=True): 629*e1fe3e4aSElliott Hughes for scope in reversed(self.scopes_): 630*e1fe3e4aSElliott Hughes item = scope.get(name) 631*e1fe3e4aSElliott Hughes if item: 632*e1fe3e4aSElliott Hughes return item 633*e1fe3e4aSElliott Hughes if case_insensitive: 634*e1fe3e4aSElliott Hughes for key in scope: 635*e1fe3e4aSElliott Hughes if key.lower() == name.lower(): 636*e1fe3e4aSElliott Hughes return scope[key] 637*e1fe3e4aSElliott Hughes return None 638*e1fe3e4aSElliott Hughes 639*e1fe3e4aSElliott Hughes 640*e1fe3e4aSElliott Hughesclass OrderedSymbolTable(SymbolTable): 641*e1fe3e4aSElliott Hughes def __init__(self): 642*e1fe3e4aSElliott Hughes self.scopes_ = [{}] 643*e1fe3e4aSElliott Hughes 644*e1fe3e4aSElliott Hughes def enter_scope(self): 645*e1fe3e4aSElliott Hughes self.scopes_.append({}) 646*e1fe3e4aSElliott Hughes 647*e1fe3e4aSElliott Hughes def resolve(self, name, case_insensitive=False): 648*e1fe3e4aSElliott Hughes SymbolTable.resolve(self, name, case_insensitive=case_insensitive) 649*e1fe3e4aSElliott Hughes 650*e1fe3e4aSElliott Hughes def range(self, start, end): 651*e1fe3e4aSElliott Hughes for scope in reversed(self.scopes_): 652*e1fe3e4aSElliott Hughes if start in scope and end in scope: 653*e1fe3e4aSElliott Hughes start_idx = list(scope.keys()).index(start) 654*e1fe3e4aSElliott Hughes end_idx = list(scope.keys()).index(end) 655*e1fe3e4aSElliott Hughes return list(scope.keys())[start_idx : end_idx + 1] 656*e1fe3e4aSElliott Hughes return None 657