diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py index 369524108..56f1f4f68 100644 --- a/Lib/fontTools/feaLib/lexer.py +++ b/Lib/fontTools/feaLib/lexer.py @@ -6,6 +6,9 @@ import os class Lexer(object): NUMBER = "NUMBER" + HEXADECIMAL = "HEXADECIMAL" + OCTAL = "OCTAL" + NUMBERS = (NUMBER, HEXADECIMAL, OCTAL) FLOAT = "FLOAT" STRING = "STRING" NAME = "NAME" @@ -123,10 +126,10 @@ class Lexer(object): if cur_char == "0" and next_char in "xX": self.pos_ += 2 self.scan_over_(Lexer.CHAR_HEXDIGIT_) - return (Lexer.NUMBER, int(text[start:self.pos_], 16), location) + return (Lexer.HEXADECIMAL, int(text[start:self.pos_], 16), location) if cur_char == "0" and next_char in Lexer.CHAR_DIGIT_: self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.NUMBER, int(text[start:self.pos_], 8), location) + return (Lexer.OCTAL, int(text[start:self.pos_], 8), location) if cur_char in Lexer.CHAR_DIGIT_: self.scan_over_(Lexer.CHAR_DIGIT_) if self.pos_ >= limit or text[self.pos_] != ".": diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 0ea35a678..d8670d6bd 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -976,14 +976,14 @@ class Parser(object): def parse_name_(self): platEncID = None langID = None - if self.next_token_type_ == Lexer.NUMBER: - platformID = self.expect_number_() + if self.next_token_type_ in Lexer.NUMBERS: + platformID = self.expect_any_number_() location = self.cur_token_location_ if platformID not in (1, 3): raise FeatureLibError("Expected platform id 1 or 3", location) - if self.next_token_type_ == Lexer.NUMBER: - platEncID = self.expect_number_() - langID = self.expect_number_() + if self.next_token_type_ in Lexer.NUMBERS: + platEncID = self.expect_any_number_() + langID = self.expect_any_number_() else: platformID = 3 location = self.cur_token_location_ @@ -1006,7 +1006,7 @@ class Parser(object): def parse_nameid_(self): assert self.cur_token_ == "nameid", self.cur_token_ - location, nameID = self.cur_token_location_, self.expect_number_() + location, nameID = self.cur_token_location_, self.expect_any_number_() if nameID > 32767: raise FeatureLibError("Name id value cannot be greater than 32767", self.cur_token_location_) @@ -1350,7 +1350,7 @@ class Parser(object): def parse_cvCharacter_(self, tag): assert self.cur_token_ == "Character", self.cur_token_ - location, character = self.cur_token_location_, self.expect_decimal_or_hexadecimal_() + location, character = self.cur_token_location_, self.expect_any_number_() self.expect_symbol_(";") if not (0xFFFFFF >= character >= 0): raise FeatureLibError("Character value must be between " @@ -1556,13 +1556,19 @@ class Parser(object): return self.cur_token_ raise FeatureLibError("Expected a name", self.cur_token_location_) - # TODO: Don't allow this method to accept hexadecimal values def expect_number_(self): self.advance_lexer_() if self.cur_token_type_ is Lexer.NUMBER: return self.cur_token_ raise FeatureLibError("Expected a number", self.cur_token_location_) + def expect_any_number_(self): + self.advance_lexer_() + if self.cur_token_type_ in Lexer.NUMBERS: + return self.cur_token_ + raise FeatureLibError("Expected a decimal, hexadecimal or octal number", + self.cur_token_location_) + def expect_float_(self): self.advance_lexer_() if self.cur_token_type_ is Lexer.FLOAT: @@ -1570,7 +1576,6 @@ class Parser(object): raise FeatureLibError("Expected a floating-point number", self.cur_token_location_) - # TODO: Don't allow this method to accept hexadecimal values def expect_decipoint_(self): if self.next_token_type_ == Lexer.FLOAT: return self.expect_float_() @@ -1580,18 +1585,6 @@ class Parser(object): raise FeatureLibError("Expected an integer or floating-point number", self.cur_token_location_) - def expect_decimal_or_hexadecimal_(self): - # the lexer returns the same token type 'NUMBER' for either decimal or - # hexadecimal integers, and casts them both to a `int` type, so it's - # impossible to distinguish the two here. This method is implemented - # the same as `expect_number_`, only it gives a more informative - # error message - self.advance_lexer_() - if self.cur_token_type_ is Lexer.NUMBER: - return self.cur_token_ - raise FeatureLibError("Expected a decimal or hexadecimal number", - self.cur_token_location_) - def expect_string_(self): self.advance_lexer_() if self.cur_token_type_ is Lexer.STRING: diff --git a/Tests/feaLib/lexer_test.py b/Tests/feaLib/lexer_test.py index 5889d35ea..f8f8e75c9 100644 --- a/Tests/feaLib/lexer_test.py +++ b/Tests/feaLib/lexer_test.py @@ -67,9 +67,9 @@ class LexerTest(unittest.TestCase): def test_number(self): self.assertEqual(lex("123 -456"), [(Lexer.NUMBER, 123), (Lexer.NUMBER, -456)]) - self.assertEqual(lex("0xCAFED00D"), [(Lexer.NUMBER, 0xCAFED00D)]) - self.assertEqual(lex("0xcafed00d"), [(Lexer.NUMBER, 0xCAFED00D)]) - self.assertEqual(lex("010"), [(Lexer.NUMBER, 0o10)]) + self.assertEqual(lex("0xCAFED00D"), [(Lexer.HEXADECIMAL, 0xCAFED00D)]) + self.assertEqual(lex("0xcafed00d"), [(Lexer.HEXADECIMAL, 0xCAFED00D)]) + self.assertEqual(lex("010"), [(Lexer.OCTAL, 0o10)]) def test_float(self): self.assertEqual(lex("1.23 -4.5"), diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py index 713bec550..a5b9f69c8 100644 --- a/Tests/feaLib/parser_test.py +++ b/Tests/feaLib/parser_test.py @@ -1117,6 +1117,36 @@ class ParserTest(unittest.TestCase): FeatureLibError, "Expected platform id 1 or 3", self.parse, 'table name { nameid 9 666 "Foo"; } name;') + def test_nameid_hexadecimal(self): + doc = self.parse( + r'table name { nameid 0x9 0x3 0x1 0x0409 "Test"; } name;') + name = doc.statements[0].statements[0] + self.assertEqual(name.nameID, 9) + self.assertEqual(name.platformID, 3) + self.assertEqual(name.platEncID, 1) + self.assertEqual(name.langID, 0x0409) + + def test_nameid_octal(self): + doc = self.parse( + r'table name { nameid 011 03 012 02011 "Test"; } name;') + name = doc.statements[0].statements[0] + self.assertEqual(name.nameID, 9) + self.assertEqual(name.platformID, 3) + self.assertEqual(name.platEncID, 10) + self.assertEqual(name.langID, 0o2011) + + def test_cv_hexadecimal(self): + doc = self.parse( + r'feature cv01 { cvParameters { Character 0x5DDE; }; } cv01;') + cv = doc.statements[0].statements[0].statements[0] + self.assertEqual(cv.character, 0x5DDE) + + def test_cv_octal(self): + doc = self.parse( + r'feature cv01 { cvParameters { Character 056736; }; } cv01;') + cv = doc.statements[0].statements[0].statements[0] + self.assertEqual(cv.character, 0o56736) + def test_rsub_format_a(self): doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;") rsub = doc.statements[0].statements[0]