def __next__(self): # Python 3 while self.lexers_: lexer = self.lexers_[-1] try: token_type, token, location = next(lexer) except StopIteration: self.lexers_.pop() continue if token_type is Lexer.NAME and token == "include": fname_type, fname_token, fname_location = lexer.next() if fname_type is not Lexer.FILENAME: raise FeatureLibError("Expected file name", fname_location) #semi_type, semi_token, semi_location = lexer.next() #if semi_type is not Lexer.SYMBOL or semi_token != ";": # raise FeatureLibError("Expected ';'", semi_location) curpath = os.path.dirname(self.featurefilepath) path = os.path.join(curpath, fname_token) if len(self.lexers_) >= 5: raise FeatureLibError("Too many recursive includes", fname_location) self.lexers_.append(self.make_lexer_(path, fname_location)) continue else: return (token_type, token, location) raise StopIteration()
def next_(self, recurse=False): if len(self.tokens) and not recurse: t = self.tokens.pop(0) if t[0] != VARIABLE: return (t[0], t[1], self.location_()) return self.parse_variable(t[1]) try: return Lexer.next_(self) except FeatureLibError as e: if u"Unexpected character" not in str(e): raise e location = self.location_() text = self.text_ start = self.pos_ cur_char = text[start] if cur_char == '$': self.pos_ += 1 self.scan_over_(Lexer.CHAR_NAME_CONTINUATION_) varname = text[start + 1:self.pos_] if len(varname) < 1 or len(varname) > 63: raise FeatureLibError("Bad variable name length", location) return (VARIABLE, varname, location) else: raise FeatureLibError("Unexpected character: %r" % cur_char, location)
def parse_glyph_pattern_(self): prefix, glyphs, lookups, suffix = ([], [], [], []) while self.next_token_ not in {"by", "from", ";"}: gc = self.parse_glyphclass_(accept_glyphname=True) marked = False if self.next_token_ == "'": self.expect_symbol_("'") marked = True if marked: glyphs.append(gc) elif glyphs: suffix.append(gc) else: prefix.append(gc) lookup = None if self.next_token_ == "lookup": self.expect_keyword_("lookup") if not marked: raise FeatureLibError( "Lookups can only follow marked glyphs", self.cur_token_location_) lookup_name = self.expect_name_() lookup = self.lookups_.resolve(lookup_name) if lookup is None: raise FeatureLibError('Unknown lookup "%s"' % lookup_name, self.cur_token_location_) if marked: lookups.append(lookup) if not glyphs and not suffix: # eg., "sub f f i by" assert lookups == [] return ([], prefix, [None] * len(prefix), []) else: return (prefix, glyphs, lookups, suffix)
def set_language(self, location, language, include_default, required): assert (len(language) == 4) if self.cur_lookup_name_: raise FeatureLibError( "Within a named lookup block, it is not allowed " "to change the language", location) if self.cur_feature_name_ in ('aalt', 'size'): raise FeatureLibError( "Language statements are not allowed " "within \"feature %s\"" % self.cur_feature_name_, location) self.cur_lookup_ = None if include_default: langsys = set(self.get_default_language_systems_()) else: langsys = set() langsys.add((self.script_, language)) self.language_systems = frozenset(langsys) if required: key = (self.script_, language) if key in self.required_features_: raise FeatureLibError( "Language %s (script %s) has already " "specified feature %s as its required feature" % (language.strip(), self.script_.strip(), self.required_features_[key].strip()), location) self.required_features_[key] = self.cur_feature_name_
def parse_glyphclass_(self, accept_glyphname): if (accept_glyphname and self.next_token_type_ in (Lexer.NAME, Lexer.CID)): glyph = self.expect_glyph_() return ast.GlyphName(self.cur_token_location_, glyph) if self.next_token_type_ is Lexer.GLYPHCLASS: self.advance_lexer_() gc = self.glyphclasses_.resolve(self.cur_token_) if gc is None: raise FeatureLibError( "Unknown glyph class @%s" % self.cur_token_, self.cur_token_location_) if isinstance(gc, ast.MarkClass): return ast.MarkClassName(self.cur_token_location_, gc) else: return ast.GlyphClassName(self.cur_token_location_, gc) self.expect_symbol_("[") glyphs = set() location = self.cur_token_location_ while self.next_token_ != "]": if self.next_token_type_ is Lexer.NAME: glyph = self.expect_glyph_() if self.next_token_ == "-": range_location = self.cur_token_location_ range_start = glyph self.expect_symbol_("-") range_end = self.expect_glyph_() glyphs.update(self.make_glyph_range_(range_location, range_start, range_end)) else: glyphs.add(glyph) elif self.next_token_type_ is Lexer.CID: glyph = self.expect_glyph_() if self.next_token_ == "-": range_location = self.cur_token_location_ range_start = self.cur_token_ self.expect_symbol_("-") range_end = self.expect_cid_() glyphs.update(self.make_cid_range_(range_location, range_start, range_end)) else: glyphs.add("cid%05d" % self.cur_token_) elif self.next_token_type_ is Lexer.GLYPHCLASS: self.advance_lexer_() gc = self.glyphclasses_.resolve(self.cur_token_) if gc is None: raise FeatureLibError( "Unknown glyph class @%s" % self.cur_token_, self.cur_token_location_) glyphs.update(gc.glyphSet()) else: raise FeatureLibError( "Expected glyph name, glyph range, " "or glyph class reference", self.next_token_location_) self.expect_symbol_("]") return ast.GlyphClass(location, glyphs)
def expect_tag_(self): self.advance_lexer_() if self.cur_token_type_ is not Lexer.NAME: raise FeatureLibError("Expected a tag", self.cur_token_location_) if len(self.cur_token_) > 4: raise FeatureLibError("Tags can not be longer than 4 characters", self.cur_token_location_) return (self.cur_token_ + " ")[:4]
def expect_markClass_reference_(self): name = self.expect_class_name_() mc = self.glyphclasses_.resolve(name) if mc is None: raise FeatureLibError("Unknown markClass @%s" % name, self.cur_token_location_) if not isinstance(mc, ast.MarkClass): raise FeatureLibError("@%s is not a markClass" % name, self.cur_token_location_) return mc
def parse_block_(self, block, vertical): self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() statements = block.statements while self.next_token_ != "}": self.advance_lexer_() if self.cur_token_type_ is Lexer.GLYPHCLASS: statements.append(self.parse_glyphclass_definition_()) elif self.is_cur_keyword_("anchorDef"): statements.append(self.parse_anchordef_()) elif self.is_cur_keyword_({"enum", "enumerate"}): statements.append(self.parse_enumerate_(vertical=vertical)) elif self.is_cur_keyword_("feature"): statements.append(self.parse_feature_reference_()) elif self.is_cur_keyword_("ignore"): statements.append(self.parse_ignore_()) elif self.is_cur_keyword_("language"): statements.append(self.parse_language_()) elif self.is_cur_keyword_("lookup"): statements.append(self.parse_lookup_(vertical)) elif self.is_cur_keyword_("lookupflag"): statements.append(self.parse_lookupflag_()) elif self.is_cur_keyword_("markClass"): statements.append(self.parse_markClass_()) elif self.is_cur_keyword_({"pos", "position"}): statements.append( self.parse_position_(enumerated=False, vertical=vertical)) elif self.is_cur_keyword_("script"): statements.append(self.parse_script_()) elif (self.is_cur_keyword_({"sub", "substitute", "rsub", "reversesub"})): statements.append(self.parse_substitute_()) elif self.is_cur_keyword_("subtable"): statements.append(self.parse_subtable_()) elif self.is_cur_keyword_("valueRecordDef"): statements.append(self.parse_valuerecord_definition_(vertical)) elif self.cur_token_ == ";": continue else: raise FeatureLibError( "Expected glyph class definition or statement", self.cur_token_location_) self.expect_symbol_("}") for symtab in self.symbol_tables_: symtab.exit_scope() name = self.expect_name_() if name != block.name.strip(): raise FeatureLibError("Expected \"%s\"" % block.name.strip(), self.cur_token_location_) self.expect_symbol_(";")
def start_lookup_block(self, location, name): if name in self.named_lookups_: raise FeatureLibError( 'Lookup "%s" has already been defined' % name, location) if self.cur_feature_name_ == "aalt": raise FeatureLibError( "Lookup blocks cannot be placed inside 'aalt' features; " "move it out, and then refer to it with a lookup statement", location) self.cur_lookup_name_ = name self.named_lookups_[name] = None self.cur_lookup_ = None
def expect_glyph_(self): self.advance_lexer_() if self.cur_token_type_ is Lexer.NAME: if len(self.cur_token_) > 63: raise FeatureLibError( "Glyph names must not be longer than 63 characters", self.cur_token_location_) return self.cur_token_ elif self.cur_token_type_ is Lexer.CID: return "cid%05d" % self.cur_token_ raise FeatureLibError("Expected a glyph name or CID", self.cur_token_location_)
def add_language_system(self, location, script, language): # OpenType Feature File Specification, section 4.b.i if (script == "DFLT" and language == "dflt" and self.default_language_systems_): raise FeatureLibError( 'If "languagesystem DFLT dflt" is present, it must be ' 'the first of the languagesystem statements', location) if (script, language) in self.default_language_systems_: raise FeatureLibError( '"languagesystem %s %s" has already been specified' % (script.strip(), language.strip()), location) self.default_language_systems_.add((script, language))
def parse_valuerecord_(self, vertical): if self.next_token_type_ is Lexer.NUMBER: number, location = self.expect_number_(), self.cur_token_location_ if vertical: val = ast.ValueRecord(location, 0, 0, 0, number, None, None, None, None) else: val = ast.ValueRecord(location, 0, 0, number, 0, None, None, None, None) return val self.expect_symbol_("<") location = self.cur_token_location_ if self.next_token_type_ is Lexer.NAME: name = self.expect_name_() if name == "NULL": self.expect_symbol_(">") return None vrd = self.valuerecords_.resolve(name) if vrd is None: raise FeatureLibError("Unknown valueRecordDef \"%s\"" % name, self.cur_token_location_) value = vrd.value xPlacement, yPlacement = (value.xPlacement, value.yPlacement) xAdvance, yAdvance = (value.xAdvance, value.yAdvance) else: xPlacement, yPlacement, xAdvance, yAdvance = ( self.expect_number_(), self.expect_number_(), self.expect_number_(), self.expect_number_()) if self.next_token_ == "<": xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = ( self.parse_device_(), self.parse_device_(), self.parse_device_(), self.parse_device_()) allDeltas = sorted([ delta for size, delta in (xPlaDevice if xPlaDevice else ()) + (yPlaDevice if yPlaDevice else ()) + (xAdvDevice if xAdvDevice else ()) + (yAdvDevice if yAdvDevice else ())]) if allDeltas[0] < -128 or allDeltas[-1] > 127: raise FeatureLibError( "Device value out of valid range (-128..127)", self.cur_token_location_) else: xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice = ( None, None, None, None) self.expect_symbol_(">") return ast.ValueRecord( location, xPlacement, yPlacement, xAdvance, yAdvance, xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice)
def parseDoStatement_(self): location = self.cur_token_location_ substatements = [] while self.next_token_type_ is not Lexer.SYMBOL or self.next_token_ != "{": self.advance_lexer_() if self.is_cur_keyword_("for"): substatements.append(self.parseDoFor_()) elif self.is_cur_keyword_("let"): substatements.append(self.parseDoLet_()) elif self.is_cur_keyword_("if"): substatements.append(self.parseDoIf_()) else: raise FeatureLibError("Unknown substatement type in do statement", self.cur_token_location_) block = self.collect_block_() lex = self.lexer_.lexers_[-1] res = self.ast.Block() keep = (self.next_token_type_, self.next_token_) block = [keep] + block + [keep] for s in self.DoIterateValues_(substatements): lex.scope = s lex.tokens = block[:] self.advance_lexer_() self.advance_lexer_() self.parse_subblock_(res, False) return res
def add_multiple_subst(self, location, glyph, replacements): lookup = self.get_lookup_(location, MultipleSubstBuilder) if glyph in lookup.mapping: raise FeatureLibError( 'Already defined substitution for glyph "%s"' % glyph, location) lookup.mapping[glyph] = replacements
def start_lookup_block(self, location, name): if name in self.named_lookups_: raise FeatureLibError( 'Lookup "%s" has already been defined' % name, location) self.cur_lookup_name_ = name self.named_lookups_[name] = None self.cur_lookup_ = None
def parse_valuerecord_(self, vertical): if self.next_token_type_ is Lexer.NUMBER: number, location = self.expect_number_(), self.cur_token_location_ if vertical: val = ast.ValueRecord(location, 0, 0, 0, number) else: val = ast.ValueRecord(location, 0, 0, number, 0) return val self.expect_symbol_("<") location = self.cur_token_location_ if self.next_token_type_ is Lexer.NAME: name = self.expect_name_() vrd = self.valuerecords_.resolve(name) if vrd is None: raise FeatureLibError("Unknown valueRecordDef \"%s\"" % name, self.cur_token_location_) value = vrd.value xPlacement, yPlacement = (value.xPlacement, value.yPlacement) xAdvance, yAdvance = (value.xAdvance, value.yAdvance) else: xPlacement, yPlacement, xAdvance, yAdvance = ( self.expect_number_(), self.expect_number_(), self.expect_number_(), self.expect_number_()) self.expect_symbol_(">") return ast.ValueRecord(location, xPlacement, yPlacement, xAdvance, yAdvance)
def set_script(self, location, script): if self.cur_lookup_name_: raise FeatureLibError( "Within a named lookup block, it is not allowed " "to change the script", location) if self.cur_feature_name_ in ('aalt', 'size'): raise FeatureLibError( "Script statements are not allowed " "within \"feature %s\"" % self.cur_feature_name_, location) self.cur_lookup_ = None self.script_ = script self.lookup_flag_ = 0 self.set_language(location, "dflt", include_default=True, required=False)
def parse(self): statements = self.doc_.statements while self.next_token_type_ is not None: self.advance_lexer_() if self.cur_token_type_ is Lexer.GLYPHCLASS: statements.append(self.parse_glyphclass_definition_()) elif self.is_cur_keyword_("anchorDef"): statements.append(self.parse_anchordef_()) elif self.is_cur_keyword_("languagesystem"): statements.append(self.parse_languagesystem_()) elif self.is_cur_keyword_("lookup"): statements.append(self.parse_lookup_(vertical=False)) elif self.is_cur_keyword_("markClass"): statements.append(self.parse_markClass_()) elif self.is_cur_keyword_("feature"): statements.append(self.parse_feature_block_()) elif self.is_cur_keyword_("table"): statements.append(self.parse_table_()) elif self.is_cur_keyword_("valueRecordDef"): statements.append( self.parse_valuerecord_definition_(vertical=False)) else: raise FeatureLibError( "Expected feature, languagesystem, lookup, markClass, " "table, or glyph class definition", self.cur_token_location_) return self.doc_
def resolve_glyphclass(self, ap_nm): try: return self.glyphclasses_.resolve(ap_nm) except KeyError: raise FeatureLibError("Glyphclass '{}' missing".format(ap_nm), self.lexer_.location_()) return None
def parseIfClass(self): location = self.cur_token_location_ self.expect_symbol_("(") if self.next_token_type_ is Lexer.GLYPHCLASS: self.advance_lexer_() def ifClassTest(): gc = self.glyphclasses_.resolve(self.cur_token_) return gc is not None and len(gc.glyphSet()) block = self.ast.IfBlock(ifClassTest, 'ifclass', '@' + self.cur_token_, location=location) self.expect_symbol_(")") import inspect # oh this is so ugly! calledby = inspect.stack()[2][ 3] # called through lambda since extension if calledby == 'parse_block_': self.parse_subblock_(block, False) else: self.parse_statements_block_(block) return block else: raise FeatureLibError("Syntax error missing glyphclass", location)
def expect_script_tag_(self): tag = self.expect_tag_() if tag == "dflt": raise FeatureLibError( '"dflt" is not a valid script tag; use "DFLT" instead', self.cur_token_location_) return tag
def expect_language_tag_(self): tag = self.expect_tag_() if tag == "DFLT": raise FeatureLibError( '"DFLT" is not a valid language tag; use "dflt" instead', self.cur_token_location_) return tag
def parseDoLet_(self): location = self.cur_token_location_ self.advance_lexer_() if self.cur_token_type_ is Lexer.NAME: name = self.cur_token_ else: raise FeatureLibError("Bad name in do let statement", location) if self.next_token_type_ != Lexer.SYMBOL or self.next_token_ != "=": raise FeatureLibError("Missing = in do let statement", self.next_token_location_) lex = self.lexer_.lexers_[-1] lex.scan_over_(Lexer.CHAR_WHITESPACE_) start = lex.pos_ lex.scan_until_(";") expr = lex.text_[start:lex.pos_] self.advance_lexer_() self.expect_symbol_(";") return self.ast.DoLetSubStatement(name, expr, getattr(self, 'glyphs', None), location=location)
def add_single_subst(self, location, mapping): lookup = self.get_lookup_(location, SingleSubstBuilder) for (from_glyph, to_glyph) in mapping.items(): if from_glyph in lookup.mapping: raise FeatureLibError( 'Already defined rule for replacing glyph "%s" by "%s"' % (from_glyph, lookup.mapping[from_glyph]), location) lookup.mapping[from_glyph] = to_glyph
def setGlyphClass_(self, location, glyph, glyphClass): oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None)) if oldClass and oldClass != glyphClass: raise FeatureLibError( "Glyph %s was assigned to a different class at %s:%s:%s" % (glyph, oldLocation[0], oldLocation[1], oldLocation[2]), location) self.glyphClassDefs_[glyph] = (glyphClass, location)
def parse_FontRevision_(self): assert self.cur_token_ == "FontRevision", self.cur_token_ location, version = self.cur_token_location_, self.expect_float_() self.expect_symbol_(";") if version <= 0: raise FeatureLibError("Font revision numbers must be positive", location) return ast.FontRevisionStatement(location, version)
def add_single_pos(self, location, glyph, valuerecord): lookup = self.get_lookup_(location, SinglePosBuilder) curValue = lookup.mapping.get(glyph) if curValue is not None and curValue != valuerecord: otherLoc = valuerecord.location raise FeatureLibError( 'Already defined different position for glyph "%s" at %s:%d:%d' % (glyph, otherLoc[0], otherLoc[1], otherLoc[2]), location) lookup.mapping[glyph] = valuerecord
def make_cid_range_(self, location, start, limit): """(location, 999, 1001) --> {"cid00999", "cid01000", "cid01001"}""" result = set() if start > limit: raise FeatureLibError( "Bad range: start should be less than limit", location) for cid in range(start, limit + 1): result.add("cid%05d" % cid) return result
def parse_table_head_(self, table): statements = table.statements while self.next_token_ != "}": self.advance_lexer_() if self.is_cur_keyword_("FontRevision"): statements.append(self.parse_FontRevision_()) else: raise FeatureLibError("Expected FontRevision", self.cur_token_location_)
def parse_glyph_pattern_(self, vertical): prefix, glyphs, lookups, values, suffix = ([], [], [], [], []) hasMarks = False while self.next_token_ not in {"by", "from", ";", ","}: gc = self.parse_glyphclass_(accept_glyphname=True) marked = False if self.next_token_ == "'": self.expect_symbol_("'") hasMarks = marked = True if marked: glyphs.append(gc) elif glyphs: suffix.append(gc) else: prefix.append(gc) if self.is_next_value_(): values.append(self.parse_valuerecord_(vertical)) else: values.append(None) lookup = None if self.next_token_ == "lookup": self.expect_keyword_("lookup") if not marked: raise FeatureLibError( "Lookups can only follow marked glyphs", self.cur_token_location_) lookup_name = self.expect_name_() lookup = self.lookups_.resolve(lookup_name) if lookup is None: raise FeatureLibError( 'Unknown lookup "%s"' % lookup_name, self.cur_token_location_) if marked: lookups.append(lookup) if not glyphs and not suffix: # eg., "sub f f i by" assert lookups == [] return ([], prefix, [None] * len(prefix), values, [], hasMarks) else: assert not any(values[:len(prefix)]), values values = values[len(prefix):][:len(glyphs)] return (prefix, glyphs, lookups, values, suffix, hasMarks)