[feaLib] Implement parsing of feature blocks

This commit is contained in:
Sascha Brawer 2015-08-01 19:58:54 +02:00
parent 3936e334f1
commit 944fab8a5f
3 changed files with 61 additions and 4 deletions

View File

@ -15,6 +15,20 @@ class FeatureFile(object):
s.write(out, linesep)
class FeatureBlock(object):
def __init__(self, location, tag):
self.location = location
self.tag = tag
self.statements = []
def write(self, out, linesep):
tag = self.tag.strip()
write(out, "feature %s {%s" % (tag, linesep))
for s in self.statements:
s.write(out, linesep)
write(out, "} %s;%s" % (tag, linesep))
class GlyphClassDefinition(object):
def __init__(self, location, name, glyphs):
self.location = location

View File

@ -34,11 +34,12 @@ class Parser(object):
while self.next_token_type_ is not None:
self.advance_lexer_()
if self.cur_token_type_ is Lexer.GLYPHCLASS:
self.parse_glyphclass_definition_()
glyphclass = self.parse_glyphclass_definition_()
self.doc_.statements.append(glyphclass)
elif self.is_cur_keyword_("languagesystem"):
self.parse_languagesystem_()
elif self.is_cur_keyword_("feature"):
break # TODO: Implement
self.parse_feature_block_()
else:
raise ParserError("Expected languagesystem, feature, or "
"glyph class definition",
@ -62,7 +63,7 @@ class Parser(object):
location)
glyphclass = ast.GlyphClassDefinition(location, name, glyphs)
self.glyphclasses_[-1][name] = glyphclass
self.doc_.statements.append(glyphclass)
return glyphclass
def parse_glyphclass_reference_(self):
result = set()
@ -103,12 +104,38 @@ class Parser(object):
return result
def parse_languagesystem_(self):
assert self.cur_token_ == "languagesystem"
location = self.cur_token_location_
script, language = self.expect_tag_(), self.expect_tag_()
self.expect_symbol_(";")
langsys = ast.LanguageSystemStatement(location, script, language)
self.doc_.statements.append(langsys)
def parse_feature_block_(self):
assert self.cur_token_ == "feature"
location = self.cur_token_location_
tag = self.expect_tag_()
self.expect_symbol_("{")
self.glyphclasses_.append({})
block = ast.FeatureBlock(location, tag)
self.doc_.statements.append(block)
while self.next_token_ != "}":
self.advance_lexer_()
if self.cur_token_type_ is Lexer.GLYPHCLASS:
block.statements.append(self.parse_glyphclass_definition_())
else:
raise ParserError("Expected glyph class definition",
self.cur_token_location_)
self.expect_symbol_("}")
self.glyphclasses_.pop()
endtag = self.expect_tag_()
if tag != endtag:
raise ParserError("Expected \"%s\"" % tag.strip(),
self.cur_token_location_)
self.expect_symbol_(";")
def is_cur_keyword_(self, k):
return (self.cur_token_type_ is Lexer.NAME) and (self.cur_token_ == k)

View File

@ -89,7 +89,7 @@ class ParserTest(unittest.TestCase):
def test_glyphclass_reference(self):
[vowels_lc, vowels_uc, vowels] = self.parse(
"@Vowels.lc = [a e i o u]; @Vowels.uc = [A E I O U]; " +
"@Vowels.lc = [a e i o u]; @Vowels.uc = [A E I O U];"
"@Vowels = [@Vowels.lc @Vowels.uc y Y];").statements
self.assertEqual(vowels_lc.glyphs, set(list("aeiou")))
self.assertEqual(vowels_uc.glyphs, set(list("AEIOU")))
@ -98,6 +98,16 @@ class ParserTest(unittest.TestCase):
ParserError, "Unknown glyph class @unknown",
self.parse, "@bad = [@unknown];")
def test_glyphclass_scoping(self):
[foo, liga, smcp] = self.parse(
"@foo = [a b];"
"feature liga { @bar = [@foo l]; } liga;"
"feature smcp { @bar = [@foo s]; } smcp;"
).statements
self.assertEqual(foo.glyphs, {"a", "b"})
self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"})
self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"})
def test_languagesystem(self):
[langsys] = self.parse("languagesystem latn DEU;").statements
self.assertEqual(langsys.script, "latn")
@ -112,6 +122,12 @@ class ParserTest(unittest.TestCase):
ParserError, "longer than 4 characters",
self.parse, "languagesystem latn FOOBAR")
def test_feature_block(self):
[liga] = self.parse("feature liga {} liga;").statements
self.assertEqual(liga.tag, "liga")
# TODO: Implement the needed bits in the parser.
@unittest.expectedFailure
def test_roundtrip(self):
self.roundtrip("mini.fea")