Example #1
0
 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()
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
 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_
Example #5
0
    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)
Example #6
0
 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]
Example #7
0
 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
Example #8
0
    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_(";")
Example #9
0
 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
Example #10
0
 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_)
Example #11
0
 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))
Example #12
0
    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)
Example #13
0
 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
Example #14
0
 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
Example #15
0
 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
Example #16
0
 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)
Example #17
0
 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)
Example #18
0
 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_
Example #19
0
 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
Example #20
0
    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)
Example #21
0
 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
Example #22
0
 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
Example #23
0
 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)
Example #24
0
 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
Example #25
0
 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)
Example #26
0
 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)
Example #27
0
 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
Example #28
0
 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
Example #29
0
 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_)
Example #30
0
    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)