diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 02a6b842e..0241f94ba 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -225,25 +225,20 @@ class FeatureBlock(Block): builder.end_feature() def asFea(self, indent=""): - res = indent + "feature {} {{\n".format(self.name.strip()) - indent += SHIFT - if len(self.statements) and isinstance(self.statements[0], FeatureNameStatement): - res += indent + "featureNames {\n" - res += indent + SHIFT - res += ("\n" + indent + SHIFT).join( - [s.asFea(indent=indent + SHIFT * 2) - for s in self.statements if isinstance(s, FeatureNameStatement)]) - res += "\n" - res += indent + "};\n" + indent - res += ("\n" + indent).join( - [s.asFea(indent=indent) - for s in self.statements if not isinstance(s, FeatureNameStatement)]) - res += "\n" - else: - res += indent - res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.statements]) - res += "\n" - res += "{}}} {};\n".format(indent[:-len(SHIFT)], self.name.strip()) + res = indent + "feature %s {\n" % self.name.strip() + res += Block.asFea(self, indent=indent) + res += indent + "} %s;\n" % self.name.strip() + return res + + +class FeatureNamesBlock(Block): + def __init__(self, location): + Block.__init__(self, location) + + def asFea(self, indent=""): + res = indent + "featureNames {\n" + res += Block.asFea(self, indent=indent) + res += indent + "};\n" return res diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 5d2ebb8d2..45c4c4733 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -1152,27 +1152,30 @@ class Parser(object): def parse_featureNames_(self, tag): assert self.cur_token_ == "featureNames", self.cur_token_ + block = self.ast.FeatureNamesBlock(self.cur_token_location_) self.expect_symbol_("{") for symtab in self.symbol_tables_: symtab.enter_scope() - - statements = [] - while self.next_token_ != "}": - self.expect_keyword_("name") - location = self.cur_token_location_ - platformID, platEncID, langID, string = self.parse_name_() - statements.append( + while self.next_token_ != "}" or self.cur_comments_: + self.advance_lexer_(comments=True) + if self.cur_token_type_ is Lexer.COMMENT: + block.statements.append(self.ast.Comment(self.cur_token_location_, self.cur_token_)) + elif self.is_cur_keyword_("name"): + location = self.cur_token_location_ + platformID, platEncID, langID, string = self.parse_name_() + block.statements.append( self.ast.FeatureNameStatement(location, tag, platformID, platEncID, langID, string)) - + elif self.cur_token_ == ";": + continue + else: + raise FeatureLibError('Expected "name"', + self.cur_token_location_) self.expect_symbol_("}") - for symtab in self.symbol_tables_: symtab.exit_scope() - self.expect_symbol_(";") - - return statements + return block def parse_FontRevision_(self): assert self.cur_token_ == "FontRevision", self.cur_token_ @@ -1225,7 +1228,7 @@ class Parser(object): elif self.is_cur_keyword_("valueRecordDef"): statements.append(self.parse_valuerecord_definition_(vertical)) elif stylisticset and self.is_cur_keyword_("featureNames"): - statements.extend(self.parse_featureNames_(stylisticset)) + statements.append(self.parse_featureNames_(stylisticset)) elif size_feature and self.is_cur_keyword_("parameters"): statements.append(self.parse_size_parameters_()) elif size_feature and self.is_cur_keyword_("sizemenuname"): diff --git a/NEWS.rst b/NEWS.rst index c3b8d8a05..8fc7c6f84 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,5 +1,6 @@ - [feaLib] Added (partial) support for parsing feature file comments ``# ...`` appearing in between statements (#879). +- [feaLib] Cleaned up syntax tree for FeatureNames. - [ttLib] Added support for reading/writing ``CFF2`` table (thanks to @readroberts at Adobe), and ``TTFA`` (ttfautohint) table. diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py index 83407e550..86b57f48e 100644 --- a/Tests/feaLib/parser_test.py +++ b/Tests/feaLib/parser_test.py @@ -202,6 +202,27 @@ class ParserTest(unittest.TestCase): self.assertIsInstance(ref, ast.FeatureReferenceStatement) self.assertEqual(ref.featureName, "salt") + def test_FeatureNames_bad(self): + self.assertRaisesRegex( + FeatureLibError, 'Expected "name"', + self.parse, "feature ss01 { featureNames { feature test; } ss01;") + + def test_FeatureNames_comment(self): + [feature] = self.parse( + "feature ss01 { featureNames { # Comment\n }; } ss01;").statements + [featureNames] = feature.statements + self.assertIsInstance(featureNames, ast.FeatureNamesBlock) + [comment] = featureNames.statements + self.assertIsInstance(comment, ast.Comment) + self.assertEqual(comment.text, "# Comment") + + def test_FeatureNames_emptyStatements(self): + [feature] = self.parse( + "feature ss01 { featureNames { ;;; }; } ss01;").statements + [featureNames] = feature.statements + self.assertIsInstance(featureNames, ast.FeatureNamesBlock) + self.assertEqual(featureNames.statements, []) + def test_FontRevision(self): doc = self.parse("table head {FontRevision 2.5;} head;") s = doc.statements[0].statements[0]