xref: /aosp_15_r20/external/fonttools/Lib/fontTools/voltLib/parser.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesimport fontTools.voltLib.ast as ast
2*e1fe3e4aSElliott Hughesfrom fontTools.voltLib.lexer import Lexer
3*e1fe3e4aSElliott Hughesfrom fontTools.voltLib.error import VoltLibError
4*e1fe3e4aSElliott Hughesfrom io import open
5*e1fe3e4aSElliott Hughes
6*e1fe3e4aSElliott HughesPARSE_FUNCS = {
7*e1fe3e4aSElliott Hughes    "DEF_GLYPH": "parse_def_glyph_",
8*e1fe3e4aSElliott Hughes    "DEF_GROUP": "parse_def_group_",
9*e1fe3e4aSElliott Hughes    "DEF_SCRIPT": "parse_def_script_",
10*e1fe3e4aSElliott Hughes    "DEF_LOOKUP": "parse_def_lookup_",
11*e1fe3e4aSElliott Hughes    "DEF_ANCHOR": "parse_def_anchor_",
12*e1fe3e4aSElliott Hughes    "GRID_PPEM": "parse_ppem_",
13*e1fe3e4aSElliott Hughes    "PRESENTATION_PPEM": "parse_ppem_",
14*e1fe3e4aSElliott Hughes    "PPOSITIONING_PPEM": "parse_ppem_",
15*e1fe3e4aSElliott Hughes    "COMPILER_USEEXTENSIONLOOKUPS": "parse_noarg_option_",
16*e1fe3e4aSElliott Hughes    "COMPILER_USEPAIRPOSFORMAT2": "parse_noarg_option_",
17*e1fe3e4aSElliott Hughes    "CMAP_FORMAT": "parse_cmap_format",
18*e1fe3e4aSElliott Hughes    "DO_NOT_TOUCH_CMAP": "parse_noarg_option_",
19*e1fe3e4aSElliott Hughes}
20*e1fe3e4aSElliott Hughes
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hughesclass Parser(object):
23*e1fe3e4aSElliott Hughes    def __init__(self, path):
24*e1fe3e4aSElliott Hughes        self.doc_ = ast.VoltFile()
25*e1fe3e4aSElliott Hughes        self.glyphs_ = OrderedSymbolTable()
26*e1fe3e4aSElliott Hughes        self.groups_ = SymbolTable()
27*e1fe3e4aSElliott Hughes        self.anchors_ = {}  # dictionary of SymbolTable() keyed by glyph
28*e1fe3e4aSElliott Hughes        self.scripts_ = SymbolTable()
29*e1fe3e4aSElliott Hughes        self.langs_ = SymbolTable()
30*e1fe3e4aSElliott Hughes        self.lookups_ = SymbolTable()
31*e1fe3e4aSElliott Hughes        self.next_token_type_, self.next_token_ = (None, None)
32*e1fe3e4aSElliott Hughes        self.next_token_location_ = None
33*e1fe3e4aSElliott Hughes        self.make_lexer_(path)
34*e1fe3e4aSElliott Hughes        self.advance_lexer_()
35*e1fe3e4aSElliott Hughes
36*e1fe3e4aSElliott Hughes    def make_lexer_(self, file_or_path):
37*e1fe3e4aSElliott Hughes        if hasattr(file_or_path, "read"):
38*e1fe3e4aSElliott Hughes            filename = getattr(file_or_path, "name", None)
39*e1fe3e4aSElliott Hughes            data = file_or_path.read()
40*e1fe3e4aSElliott Hughes        else:
41*e1fe3e4aSElliott Hughes            filename = file_or_path
42*e1fe3e4aSElliott Hughes            with open(file_or_path, "r") as f:
43*e1fe3e4aSElliott Hughes                data = f.read()
44*e1fe3e4aSElliott Hughes        self.lexer_ = Lexer(data, filename)
45*e1fe3e4aSElliott Hughes
46*e1fe3e4aSElliott Hughes    def parse(self):
47*e1fe3e4aSElliott Hughes        statements = self.doc_.statements
48*e1fe3e4aSElliott Hughes        while self.next_token_type_ is not None:
49*e1fe3e4aSElliott Hughes            self.advance_lexer_()
50*e1fe3e4aSElliott Hughes            if self.cur_token_ in PARSE_FUNCS.keys():
51*e1fe3e4aSElliott Hughes                func = getattr(self, PARSE_FUNCS[self.cur_token_])
52*e1fe3e4aSElliott Hughes                statements.append(func())
53*e1fe3e4aSElliott Hughes            elif self.is_cur_keyword_("END"):
54*e1fe3e4aSElliott Hughes                break
55*e1fe3e4aSElliott Hughes            else:
56*e1fe3e4aSElliott Hughes                raise VoltLibError(
57*e1fe3e4aSElliott Hughes                    "Expected " + ", ".join(sorted(PARSE_FUNCS.keys())),
58*e1fe3e4aSElliott Hughes                    self.cur_token_location_,
59*e1fe3e4aSElliott Hughes                )
60*e1fe3e4aSElliott Hughes        return self.doc_
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughes    def parse_def_glyph_(self):
63*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_GLYPH")
64*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
65*e1fe3e4aSElliott Hughes        name = self.expect_string_()
66*e1fe3e4aSElliott Hughes        self.expect_keyword_("ID")
67*e1fe3e4aSElliott Hughes        gid = self.expect_number_()
68*e1fe3e4aSElliott Hughes        if gid < 0:
69*e1fe3e4aSElliott Hughes            raise VoltLibError("Invalid glyph ID", self.cur_token_location_)
70*e1fe3e4aSElliott Hughes        gunicode = None
71*e1fe3e4aSElliott Hughes        if self.next_token_ == "UNICODE":
72*e1fe3e4aSElliott Hughes            self.expect_keyword_("UNICODE")
73*e1fe3e4aSElliott Hughes            gunicode = [self.expect_number_()]
74*e1fe3e4aSElliott Hughes            if gunicode[0] < 0:
75*e1fe3e4aSElliott Hughes                raise VoltLibError("Invalid glyph UNICODE", self.cur_token_location_)
76*e1fe3e4aSElliott Hughes        elif self.next_token_ == "UNICODEVALUES":
77*e1fe3e4aSElliott Hughes            self.expect_keyword_("UNICODEVALUES")
78*e1fe3e4aSElliott Hughes            gunicode = self.parse_unicode_values_()
79*e1fe3e4aSElliott Hughes        gtype = None
80*e1fe3e4aSElliott Hughes        if self.next_token_ == "TYPE":
81*e1fe3e4aSElliott Hughes            self.expect_keyword_("TYPE")
82*e1fe3e4aSElliott Hughes            gtype = self.expect_name_()
83*e1fe3e4aSElliott Hughes            assert gtype in ("BASE", "LIGATURE", "MARK", "COMPONENT")
84*e1fe3e4aSElliott Hughes        components = None
85*e1fe3e4aSElliott Hughes        if self.next_token_ == "COMPONENTS":
86*e1fe3e4aSElliott Hughes            self.expect_keyword_("COMPONENTS")
87*e1fe3e4aSElliott Hughes            components = self.expect_number_()
88*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_GLYPH")
89*e1fe3e4aSElliott Hughes        if self.glyphs_.resolve(name) is not None:
90*e1fe3e4aSElliott Hughes            raise VoltLibError(
91*e1fe3e4aSElliott Hughes                'Glyph "%s" (gid %i) already defined' % (name, gid), location
92*e1fe3e4aSElliott Hughes            )
93*e1fe3e4aSElliott Hughes        def_glyph = ast.GlyphDefinition(
94*e1fe3e4aSElliott Hughes            name, gid, gunicode, gtype, components, location=location
95*e1fe3e4aSElliott Hughes        )
96*e1fe3e4aSElliott Hughes        self.glyphs_.define(name, def_glyph)
97*e1fe3e4aSElliott Hughes        return def_glyph
98*e1fe3e4aSElliott Hughes
99*e1fe3e4aSElliott Hughes    def parse_def_group_(self):
100*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_GROUP")
101*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
102*e1fe3e4aSElliott Hughes        name = self.expect_string_()
103*e1fe3e4aSElliott Hughes        enum = None
104*e1fe3e4aSElliott Hughes        if self.next_token_ == "ENUM":
105*e1fe3e4aSElliott Hughes            enum = self.parse_enum_()
106*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_GROUP")
107*e1fe3e4aSElliott Hughes        if self.groups_.resolve(name) is not None:
108*e1fe3e4aSElliott Hughes            raise VoltLibError(
109*e1fe3e4aSElliott Hughes                'Glyph group "%s" already defined, '
110*e1fe3e4aSElliott Hughes                "group names are case insensitive" % name,
111*e1fe3e4aSElliott Hughes                location,
112*e1fe3e4aSElliott Hughes            )
113*e1fe3e4aSElliott Hughes        def_group = ast.GroupDefinition(name, enum, location=location)
114*e1fe3e4aSElliott Hughes        self.groups_.define(name, def_group)
115*e1fe3e4aSElliott Hughes        return def_group
116*e1fe3e4aSElliott Hughes
117*e1fe3e4aSElliott Hughes    def parse_def_script_(self):
118*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_SCRIPT")
119*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
120*e1fe3e4aSElliott Hughes        name = None
121*e1fe3e4aSElliott Hughes        if self.next_token_ == "NAME":
122*e1fe3e4aSElliott Hughes            self.expect_keyword_("NAME")
123*e1fe3e4aSElliott Hughes            name = self.expect_string_()
124*e1fe3e4aSElliott Hughes        self.expect_keyword_("TAG")
125*e1fe3e4aSElliott Hughes        tag = self.expect_string_()
126*e1fe3e4aSElliott Hughes        if self.scripts_.resolve(tag) is not None:
127*e1fe3e4aSElliott Hughes            raise VoltLibError(
128*e1fe3e4aSElliott Hughes                'Script "%s" already defined, '
129*e1fe3e4aSElliott Hughes                "script tags are case insensitive" % tag,
130*e1fe3e4aSElliott Hughes                location,
131*e1fe3e4aSElliott Hughes            )
132*e1fe3e4aSElliott Hughes        self.langs_.enter_scope()
133*e1fe3e4aSElliott Hughes        langs = []
134*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_SCRIPT":
135*e1fe3e4aSElliott Hughes            self.advance_lexer_()
136*e1fe3e4aSElliott Hughes            lang = self.parse_langsys_()
137*e1fe3e4aSElliott Hughes            self.expect_keyword_("END_LANGSYS")
138*e1fe3e4aSElliott Hughes            if self.langs_.resolve(lang.tag) is not None:
139*e1fe3e4aSElliott Hughes                raise VoltLibError(
140*e1fe3e4aSElliott Hughes                    'Language "%s" already defined in script "%s", '
141*e1fe3e4aSElliott Hughes                    "language tags are case insensitive" % (lang.tag, tag),
142*e1fe3e4aSElliott Hughes                    location,
143*e1fe3e4aSElliott Hughes                )
144*e1fe3e4aSElliott Hughes            self.langs_.define(lang.tag, lang)
145*e1fe3e4aSElliott Hughes            langs.append(lang)
146*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_SCRIPT")
147*e1fe3e4aSElliott Hughes        self.langs_.exit_scope()
148*e1fe3e4aSElliott Hughes        def_script = ast.ScriptDefinition(name, tag, langs, location=location)
149*e1fe3e4aSElliott Hughes        self.scripts_.define(tag, def_script)
150*e1fe3e4aSElliott Hughes        return def_script
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes    def parse_langsys_(self):
153*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_LANGSYS")
154*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
155*e1fe3e4aSElliott Hughes        name = None
156*e1fe3e4aSElliott Hughes        if self.next_token_ == "NAME":
157*e1fe3e4aSElliott Hughes            self.expect_keyword_("NAME")
158*e1fe3e4aSElliott Hughes            name = self.expect_string_()
159*e1fe3e4aSElliott Hughes        self.expect_keyword_("TAG")
160*e1fe3e4aSElliott Hughes        tag = self.expect_string_()
161*e1fe3e4aSElliott Hughes        features = []
162*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_LANGSYS":
163*e1fe3e4aSElliott Hughes            self.advance_lexer_()
164*e1fe3e4aSElliott Hughes            feature = self.parse_feature_()
165*e1fe3e4aSElliott Hughes            self.expect_keyword_("END_FEATURE")
166*e1fe3e4aSElliott Hughes            features.append(feature)
167*e1fe3e4aSElliott Hughes        def_langsys = ast.LangSysDefinition(name, tag, features, location=location)
168*e1fe3e4aSElliott Hughes        return def_langsys
169*e1fe3e4aSElliott Hughes
170*e1fe3e4aSElliott Hughes    def parse_feature_(self):
171*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_FEATURE")
172*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
173*e1fe3e4aSElliott Hughes        self.expect_keyword_("NAME")
174*e1fe3e4aSElliott Hughes        name = self.expect_string_()
175*e1fe3e4aSElliott Hughes        self.expect_keyword_("TAG")
176*e1fe3e4aSElliott Hughes        tag = self.expect_string_()
177*e1fe3e4aSElliott Hughes        lookups = []
178*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_FEATURE":
179*e1fe3e4aSElliott Hughes            # self.advance_lexer_()
180*e1fe3e4aSElliott Hughes            self.expect_keyword_("LOOKUP")
181*e1fe3e4aSElliott Hughes            lookup = self.expect_string_()
182*e1fe3e4aSElliott Hughes            lookups.append(lookup)
183*e1fe3e4aSElliott Hughes        feature = ast.FeatureDefinition(name, tag, lookups, location=location)
184*e1fe3e4aSElliott Hughes        return feature
185*e1fe3e4aSElliott Hughes
186*e1fe3e4aSElliott Hughes    def parse_def_lookup_(self):
187*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_LOOKUP")
188*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
189*e1fe3e4aSElliott Hughes        name = self.expect_string_()
190*e1fe3e4aSElliott Hughes        if not name[0].isalpha():
191*e1fe3e4aSElliott Hughes            raise VoltLibError(
192*e1fe3e4aSElliott Hughes                'Lookup name "%s" must start with a letter' % name, location
193*e1fe3e4aSElliott Hughes            )
194*e1fe3e4aSElliott Hughes        if self.lookups_.resolve(name) is not None:
195*e1fe3e4aSElliott Hughes            raise VoltLibError(
196*e1fe3e4aSElliott Hughes                'Lookup "%s" already defined, '
197*e1fe3e4aSElliott Hughes                "lookup names are case insensitive" % name,
198*e1fe3e4aSElliott Hughes                location,
199*e1fe3e4aSElliott Hughes            )
200*e1fe3e4aSElliott Hughes        process_base = True
201*e1fe3e4aSElliott Hughes        if self.next_token_ == "PROCESS_BASE":
202*e1fe3e4aSElliott Hughes            self.advance_lexer_()
203*e1fe3e4aSElliott Hughes        elif self.next_token_ == "SKIP_BASE":
204*e1fe3e4aSElliott Hughes            self.advance_lexer_()
205*e1fe3e4aSElliott Hughes            process_base = False
206*e1fe3e4aSElliott Hughes        process_marks = True
207*e1fe3e4aSElliott Hughes        mark_glyph_set = None
208*e1fe3e4aSElliott Hughes        if self.next_token_ == "PROCESS_MARKS":
209*e1fe3e4aSElliott Hughes            self.advance_lexer_()
210*e1fe3e4aSElliott Hughes            if self.next_token_ == "MARK_GLYPH_SET":
211*e1fe3e4aSElliott Hughes                self.advance_lexer_()
212*e1fe3e4aSElliott Hughes                mark_glyph_set = self.expect_string_()
213*e1fe3e4aSElliott Hughes            elif self.next_token_ == "ALL":
214*e1fe3e4aSElliott Hughes                self.advance_lexer_()
215*e1fe3e4aSElliott Hughes            elif self.next_token_ == "NONE":
216*e1fe3e4aSElliott Hughes                self.advance_lexer_()
217*e1fe3e4aSElliott Hughes                process_marks = False
218*e1fe3e4aSElliott Hughes            elif self.next_token_type_ == Lexer.STRING:
219*e1fe3e4aSElliott Hughes                process_marks = self.expect_string_()
220*e1fe3e4aSElliott Hughes            else:
221*e1fe3e4aSElliott Hughes                raise VoltLibError(
222*e1fe3e4aSElliott Hughes                    "Expected ALL, NONE, MARK_GLYPH_SET or an ID. "
223*e1fe3e4aSElliott Hughes                    "Got %s" % (self.next_token_type_),
224*e1fe3e4aSElliott Hughes                    location,
225*e1fe3e4aSElliott Hughes                )
226*e1fe3e4aSElliott Hughes        elif self.next_token_ == "SKIP_MARKS":
227*e1fe3e4aSElliott Hughes            self.advance_lexer_()
228*e1fe3e4aSElliott Hughes            process_marks = False
229*e1fe3e4aSElliott Hughes        direction = None
230*e1fe3e4aSElliott Hughes        if self.next_token_ == "DIRECTION":
231*e1fe3e4aSElliott Hughes            self.expect_keyword_("DIRECTION")
232*e1fe3e4aSElliott Hughes            direction = self.expect_name_()
233*e1fe3e4aSElliott Hughes            assert direction in ("LTR", "RTL")
234*e1fe3e4aSElliott Hughes        reversal = None
235*e1fe3e4aSElliott Hughes        if self.next_token_ == "REVERSAL":
236*e1fe3e4aSElliott Hughes            self.expect_keyword_("REVERSAL")
237*e1fe3e4aSElliott Hughes            reversal = True
238*e1fe3e4aSElliott Hughes        comments = None
239*e1fe3e4aSElliott Hughes        if self.next_token_ == "COMMENTS":
240*e1fe3e4aSElliott Hughes            self.expect_keyword_("COMMENTS")
241*e1fe3e4aSElliott Hughes            comments = self.expect_string_().replace(r"\n", "\n")
242*e1fe3e4aSElliott Hughes        context = []
243*e1fe3e4aSElliott Hughes        while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"):
244*e1fe3e4aSElliott Hughes            context = self.parse_context_()
245*e1fe3e4aSElliott Hughes        as_pos_or_sub = self.expect_name_()
246*e1fe3e4aSElliott Hughes        sub = None
247*e1fe3e4aSElliott Hughes        pos = None
248*e1fe3e4aSElliott Hughes        if as_pos_or_sub == "AS_SUBSTITUTION":
249*e1fe3e4aSElliott Hughes            sub = self.parse_substitution_(reversal)
250*e1fe3e4aSElliott Hughes        elif as_pos_or_sub == "AS_POSITION":
251*e1fe3e4aSElliott Hughes            pos = self.parse_position_()
252*e1fe3e4aSElliott Hughes        else:
253*e1fe3e4aSElliott Hughes            raise VoltLibError(
254*e1fe3e4aSElliott Hughes                "Expected AS_SUBSTITUTION or AS_POSITION. " "Got %s" % (as_pos_or_sub),
255*e1fe3e4aSElliott Hughes                location,
256*e1fe3e4aSElliott Hughes            )
257*e1fe3e4aSElliott Hughes        def_lookup = ast.LookupDefinition(
258*e1fe3e4aSElliott Hughes            name,
259*e1fe3e4aSElliott Hughes            process_base,
260*e1fe3e4aSElliott Hughes            process_marks,
261*e1fe3e4aSElliott Hughes            mark_glyph_set,
262*e1fe3e4aSElliott Hughes            direction,
263*e1fe3e4aSElliott Hughes            reversal,
264*e1fe3e4aSElliott Hughes            comments,
265*e1fe3e4aSElliott Hughes            context,
266*e1fe3e4aSElliott Hughes            sub,
267*e1fe3e4aSElliott Hughes            pos,
268*e1fe3e4aSElliott Hughes            location=location,
269*e1fe3e4aSElliott Hughes        )
270*e1fe3e4aSElliott Hughes        self.lookups_.define(name, def_lookup)
271*e1fe3e4aSElliott Hughes        return def_lookup
272*e1fe3e4aSElliott Hughes
273*e1fe3e4aSElliott Hughes    def parse_context_(self):
274*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
275*e1fe3e4aSElliott Hughes        contexts = []
276*e1fe3e4aSElliott Hughes        while self.next_token_ in ("EXCEPT_CONTEXT", "IN_CONTEXT"):
277*e1fe3e4aSElliott Hughes            side = None
278*e1fe3e4aSElliott Hughes            coverage = None
279*e1fe3e4aSElliott Hughes            ex_or_in = self.expect_name_()
280*e1fe3e4aSElliott Hughes            # side_contexts = [] # XXX
281*e1fe3e4aSElliott Hughes            if self.next_token_ != "END_CONTEXT":
282*e1fe3e4aSElliott Hughes                left = []
283*e1fe3e4aSElliott Hughes                right = []
284*e1fe3e4aSElliott Hughes                while self.next_token_ in ("LEFT", "RIGHT"):
285*e1fe3e4aSElliott Hughes                    side = self.expect_name_()
286*e1fe3e4aSElliott Hughes                    coverage = self.parse_coverage_()
287*e1fe3e4aSElliott Hughes                    if side == "LEFT":
288*e1fe3e4aSElliott Hughes                        left.append(coverage)
289*e1fe3e4aSElliott Hughes                    else:
290*e1fe3e4aSElliott Hughes                        right.append(coverage)
291*e1fe3e4aSElliott Hughes                self.expect_keyword_("END_CONTEXT")
292*e1fe3e4aSElliott Hughes                context = ast.ContextDefinition(
293*e1fe3e4aSElliott Hughes                    ex_or_in, left, right, location=location
294*e1fe3e4aSElliott Hughes                )
295*e1fe3e4aSElliott Hughes                contexts.append(context)
296*e1fe3e4aSElliott Hughes            else:
297*e1fe3e4aSElliott Hughes                self.expect_keyword_("END_CONTEXT")
298*e1fe3e4aSElliott Hughes        return contexts
299*e1fe3e4aSElliott Hughes
300*e1fe3e4aSElliott Hughes    def parse_substitution_(self, reversal):
301*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("AS_SUBSTITUTION")
302*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
303*e1fe3e4aSElliott Hughes        src = []
304*e1fe3e4aSElliott Hughes        dest = []
305*e1fe3e4aSElliott Hughes        if self.next_token_ != "SUB":
306*e1fe3e4aSElliott Hughes            raise VoltLibError("Expected SUB", location)
307*e1fe3e4aSElliott Hughes        while self.next_token_ == "SUB":
308*e1fe3e4aSElliott Hughes            self.expect_keyword_("SUB")
309*e1fe3e4aSElliott Hughes            src.append(self.parse_coverage_())
310*e1fe3e4aSElliott Hughes            self.expect_keyword_("WITH")
311*e1fe3e4aSElliott Hughes            dest.append(self.parse_coverage_())
312*e1fe3e4aSElliott Hughes            self.expect_keyword_("END_SUB")
313*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_SUBSTITUTION")
314*e1fe3e4aSElliott Hughes        max_src = max([len(cov) for cov in src])
315*e1fe3e4aSElliott Hughes        max_dest = max([len(cov) for cov in dest])
316*e1fe3e4aSElliott Hughes        # many to many or mixed is invalid
317*e1fe3e4aSElliott Hughes        if (max_src > 1 and max_dest > 1) or (
318*e1fe3e4aSElliott Hughes            reversal and (max_src > 1 or max_dest > 1)
319*e1fe3e4aSElliott Hughes        ):
320*e1fe3e4aSElliott Hughes            raise VoltLibError("Invalid substitution type", location)
321*e1fe3e4aSElliott Hughes        mapping = dict(zip(tuple(src), tuple(dest)))
322*e1fe3e4aSElliott Hughes        if max_src == 1 and max_dest == 1:
323*e1fe3e4aSElliott Hughes            if reversal:
324*e1fe3e4aSElliott Hughes                sub = ast.SubstitutionReverseChainingSingleDefinition(
325*e1fe3e4aSElliott Hughes                    mapping, location=location
326*e1fe3e4aSElliott Hughes                )
327*e1fe3e4aSElliott Hughes            else:
328*e1fe3e4aSElliott Hughes                sub = ast.SubstitutionSingleDefinition(mapping, location=location)
329*e1fe3e4aSElliott Hughes        elif max_src == 1 and max_dest > 1:
330*e1fe3e4aSElliott Hughes            sub = ast.SubstitutionMultipleDefinition(mapping, location=location)
331*e1fe3e4aSElliott Hughes        elif max_src > 1 and max_dest == 1:
332*e1fe3e4aSElliott Hughes            sub = ast.SubstitutionLigatureDefinition(mapping, location=location)
333*e1fe3e4aSElliott Hughes        return sub
334*e1fe3e4aSElliott Hughes
335*e1fe3e4aSElliott Hughes    def parse_position_(self):
336*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("AS_POSITION")
337*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
338*e1fe3e4aSElliott Hughes        pos_type = self.expect_name_()
339*e1fe3e4aSElliott Hughes        if pos_type not in ("ATTACH", "ATTACH_CURSIVE", "ADJUST_PAIR", "ADJUST_SINGLE"):
340*e1fe3e4aSElliott Hughes            raise VoltLibError(
341*e1fe3e4aSElliott Hughes                "Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE", location
342*e1fe3e4aSElliott Hughes            )
343*e1fe3e4aSElliott Hughes        if pos_type == "ATTACH":
344*e1fe3e4aSElliott Hughes            position = self.parse_attach_()
345*e1fe3e4aSElliott Hughes        elif pos_type == "ATTACH_CURSIVE":
346*e1fe3e4aSElliott Hughes            position = self.parse_attach_cursive_()
347*e1fe3e4aSElliott Hughes        elif pos_type == "ADJUST_PAIR":
348*e1fe3e4aSElliott Hughes            position = self.parse_adjust_pair_()
349*e1fe3e4aSElliott Hughes        elif pos_type == "ADJUST_SINGLE":
350*e1fe3e4aSElliott Hughes            position = self.parse_adjust_single_()
351*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_POSITION")
352*e1fe3e4aSElliott Hughes        return position
353*e1fe3e4aSElliott Hughes
354*e1fe3e4aSElliott Hughes    def parse_attach_(self):
355*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("ATTACH")
356*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
357*e1fe3e4aSElliott Hughes        coverage = self.parse_coverage_()
358*e1fe3e4aSElliott Hughes        coverage_to = []
359*e1fe3e4aSElliott Hughes        self.expect_keyword_("TO")
360*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_ATTACH":
361*e1fe3e4aSElliott Hughes            cov = self.parse_coverage_()
362*e1fe3e4aSElliott Hughes            self.expect_keyword_("AT")
363*e1fe3e4aSElliott Hughes            self.expect_keyword_("ANCHOR")
364*e1fe3e4aSElliott Hughes            anchor_name = self.expect_string_()
365*e1fe3e4aSElliott Hughes            coverage_to.append((cov, anchor_name))
366*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_ATTACH")
367*e1fe3e4aSElliott Hughes        position = ast.PositionAttachDefinition(
368*e1fe3e4aSElliott Hughes            coverage, coverage_to, location=location
369*e1fe3e4aSElliott Hughes        )
370*e1fe3e4aSElliott Hughes        return position
371*e1fe3e4aSElliott Hughes
372*e1fe3e4aSElliott Hughes    def parse_attach_cursive_(self):
373*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("ATTACH_CURSIVE")
374*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
375*e1fe3e4aSElliott Hughes        coverages_exit = []
376*e1fe3e4aSElliott Hughes        coverages_enter = []
377*e1fe3e4aSElliott Hughes        while self.next_token_ != "ENTER":
378*e1fe3e4aSElliott Hughes            self.expect_keyword_("EXIT")
379*e1fe3e4aSElliott Hughes            coverages_exit.append(self.parse_coverage_())
380*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_ATTACH":
381*e1fe3e4aSElliott Hughes            self.expect_keyword_("ENTER")
382*e1fe3e4aSElliott Hughes            coverages_enter.append(self.parse_coverage_())
383*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_ATTACH")
384*e1fe3e4aSElliott Hughes        position = ast.PositionAttachCursiveDefinition(
385*e1fe3e4aSElliott Hughes            coverages_exit, coverages_enter, location=location
386*e1fe3e4aSElliott Hughes        )
387*e1fe3e4aSElliott Hughes        return position
388*e1fe3e4aSElliott Hughes
389*e1fe3e4aSElliott Hughes    def parse_adjust_pair_(self):
390*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("ADJUST_PAIR")
391*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
392*e1fe3e4aSElliott Hughes        coverages_1 = []
393*e1fe3e4aSElliott Hughes        coverages_2 = []
394*e1fe3e4aSElliott Hughes        adjust_pair = {}
395*e1fe3e4aSElliott Hughes        while self.next_token_ == "FIRST":
396*e1fe3e4aSElliott Hughes            self.advance_lexer_()
397*e1fe3e4aSElliott Hughes            coverage_1 = self.parse_coverage_()
398*e1fe3e4aSElliott Hughes            coverages_1.append(coverage_1)
399*e1fe3e4aSElliott Hughes        while self.next_token_ == "SECOND":
400*e1fe3e4aSElliott Hughes            self.advance_lexer_()
401*e1fe3e4aSElliott Hughes            coverage_2 = self.parse_coverage_()
402*e1fe3e4aSElliott Hughes            coverages_2.append(coverage_2)
403*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_ADJUST":
404*e1fe3e4aSElliott Hughes            id_1 = self.expect_number_()
405*e1fe3e4aSElliott Hughes            id_2 = self.expect_number_()
406*e1fe3e4aSElliott Hughes            self.expect_keyword_("BY")
407*e1fe3e4aSElliott Hughes            pos_1 = self.parse_pos_()
408*e1fe3e4aSElliott Hughes            pos_2 = self.parse_pos_()
409*e1fe3e4aSElliott Hughes            adjust_pair[(id_1, id_2)] = (pos_1, pos_2)
410*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_ADJUST")
411*e1fe3e4aSElliott Hughes        position = ast.PositionAdjustPairDefinition(
412*e1fe3e4aSElliott Hughes            coverages_1, coverages_2, adjust_pair, location=location
413*e1fe3e4aSElliott Hughes        )
414*e1fe3e4aSElliott Hughes        return position
415*e1fe3e4aSElliott Hughes
416*e1fe3e4aSElliott Hughes    def parse_adjust_single_(self):
417*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("ADJUST_SINGLE")
418*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
419*e1fe3e4aSElliott Hughes        adjust_single = []
420*e1fe3e4aSElliott Hughes        while self.next_token_ != "END_ADJUST":
421*e1fe3e4aSElliott Hughes            coverages = self.parse_coverage_()
422*e1fe3e4aSElliott Hughes            self.expect_keyword_("BY")
423*e1fe3e4aSElliott Hughes            pos = self.parse_pos_()
424*e1fe3e4aSElliott Hughes            adjust_single.append((coverages, pos))
425*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_ADJUST")
426*e1fe3e4aSElliott Hughes        position = ast.PositionAdjustSingleDefinition(adjust_single, location=location)
427*e1fe3e4aSElliott Hughes        return position
428*e1fe3e4aSElliott Hughes
429*e1fe3e4aSElliott Hughes    def parse_def_anchor_(self):
430*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("DEF_ANCHOR")
431*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
432*e1fe3e4aSElliott Hughes        name = self.expect_string_()
433*e1fe3e4aSElliott Hughes        self.expect_keyword_("ON")
434*e1fe3e4aSElliott Hughes        gid = self.expect_number_()
435*e1fe3e4aSElliott Hughes        self.expect_keyword_("GLYPH")
436*e1fe3e4aSElliott Hughes        glyph_name = self.expect_name_()
437*e1fe3e4aSElliott Hughes        self.expect_keyword_("COMPONENT")
438*e1fe3e4aSElliott Hughes        component = self.expect_number_()
439*e1fe3e4aSElliott Hughes        # check for duplicate anchor names on this glyph
440*e1fe3e4aSElliott Hughes        if glyph_name in self.anchors_:
441*e1fe3e4aSElliott Hughes            anchor = self.anchors_[glyph_name].resolve(name)
442*e1fe3e4aSElliott Hughes            if anchor is not None and anchor.component == component:
443*e1fe3e4aSElliott Hughes                raise VoltLibError(
444*e1fe3e4aSElliott Hughes                    'Anchor "%s" already defined, '
445*e1fe3e4aSElliott Hughes                    "anchor names are case insensitive" % name,
446*e1fe3e4aSElliott Hughes                    location,
447*e1fe3e4aSElliott Hughes                )
448*e1fe3e4aSElliott Hughes        if self.next_token_ == "LOCKED":
449*e1fe3e4aSElliott Hughes            locked = True
450*e1fe3e4aSElliott Hughes            self.advance_lexer_()
451*e1fe3e4aSElliott Hughes        else:
452*e1fe3e4aSElliott Hughes            locked = False
453*e1fe3e4aSElliott Hughes        self.expect_keyword_("AT")
454*e1fe3e4aSElliott Hughes        pos = self.parse_pos_()
455*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_ANCHOR")
456*e1fe3e4aSElliott Hughes        anchor = ast.AnchorDefinition(
457*e1fe3e4aSElliott Hughes            name, gid, glyph_name, component, locked, pos, location=location
458*e1fe3e4aSElliott Hughes        )
459*e1fe3e4aSElliott Hughes        if glyph_name not in self.anchors_:
460*e1fe3e4aSElliott Hughes            self.anchors_[glyph_name] = SymbolTable()
461*e1fe3e4aSElliott Hughes        self.anchors_[glyph_name].define(name, anchor)
462*e1fe3e4aSElliott Hughes        return anchor
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes    def parse_adjust_by_(self):
465*e1fe3e4aSElliott Hughes        self.advance_lexer_()
466*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("ADJUST_BY")
467*e1fe3e4aSElliott Hughes        adjustment = self.expect_number_()
468*e1fe3e4aSElliott Hughes        self.expect_keyword_("AT")
469*e1fe3e4aSElliott Hughes        size = self.expect_number_()
470*e1fe3e4aSElliott Hughes        return adjustment, size
471*e1fe3e4aSElliott Hughes
472*e1fe3e4aSElliott Hughes    def parse_pos_(self):
473*e1fe3e4aSElliott Hughes        # VOLT syntax doesn't seem to take device Y advance
474*e1fe3e4aSElliott Hughes        self.advance_lexer_()
475*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
476*e1fe3e4aSElliott Hughes        assert self.is_cur_keyword_("POS"), location
477*e1fe3e4aSElliott Hughes        adv = None
478*e1fe3e4aSElliott Hughes        dx = None
479*e1fe3e4aSElliott Hughes        dy = None
480*e1fe3e4aSElliott Hughes        adv_adjust_by = {}
481*e1fe3e4aSElliott Hughes        dx_adjust_by = {}
482*e1fe3e4aSElliott Hughes        dy_adjust_by = {}
483*e1fe3e4aSElliott Hughes        if self.next_token_ == "ADV":
484*e1fe3e4aSElliott Hughes            self.advance_lexer_()
485*e1fe3e4aSElliott Hughes            adv = self.expect_number_()
486*e1fe3e4aSElliott Hughes            while self.next_token_ == "ADJUST_BY":
487*e1fe3e4aSElliott Hughes                adjustment, size = self.parse_adjust_by_()
488*e1fe3e4aSElliott Hughes                adv_adjust_by[size] = adjustment
489*e1fe3e4aSElliott Hughes        if self.next_token_ == "DX":
490*e1fe3e4aSElliott Hughes            self.advance_lexer_()
491*e1fe3e4aSElliott Hughes            dx = self.expect_number_()
492*e1fe3e4aSElliott Hughes            while self.next_token_ == "ADJUST_BY":
493*e1fe3e4aSElliott Hughes                adjustment, size = self.parse_adjust_by_()
494*e1fe3e4aSElliott Hughes                dx_adjust_by[size] = adjustment
495*e1fe3e4aSElliott Hughes        if self.next_token_ == "DY":
496*e1fe3e4aSElliott Hughes            self.advance_lexer_()
497*e1fe3e4aSElliott Hughes            dy = self.expect_number_()
498*e1fe3e4aSElliott Hughes            while self.next_token_ == "ADJUST_BY":
499*e1fe3e4aSElliott Hughes                adjustment, size = self.parse_adjust_by_()
500*e1fe3e4aSElliott Hughes                dy_adjust_by[size] = adjustment
501*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_POS")
502*e1fe3e4aSElliott Hughes        return ast.Pos(adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by)
503*e1fe3e4aSElliott Hughes
504*e1fe3e4aSElliott Hughes    def parse_unicode_values_(self):
505*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
506*e1fe3e4aSElliott Hughes        try:
507*e1fe3e4aSElliott Hughes            unicode_values = self.expect_string_().split(",")
508*e1fe3e4aSElliott Hughes            unicode_values = [int(uni[2:], 16) for uni in unicode_values if uni != ""]
509*e1fe3e4aSElliott Hughes        except ValueError as err:
510*e1fe3e4aSElliott Hughes            raise VoltLibError(str(err), location)
511*e1fe3e4aSElliott Hughes        return unicode_values if unicode_values != [] else None
512*e1fe3e4aSElliott Hughes
513*e1fe3e4aSElliott Hughes    def parse_enum_(self):
514*e1fe3e4aSElliott Hughes        self.expect_keyword_("ENUM")
515*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
516*e1fe3e4aSElliott Hughes        enum = ast.Enum(self.parse_coverage_(), location=location)
517*e1fe3e4aSElliott Hughes        self.expect_keyword_("END_ENUM")
518*e1fe3e4aSElliott Hughes        return enum
519*e1fe3e4aSElliott Hughes
520*e1fe3e4aSElliott Hughes    def parse_coverage_(self):
521*e1fe3e4aSElliott Hughes        coverage = []
522*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
523*e1fe3e4aSElliott Hughes        while self.next_token_ in ("GLYPH", "GROUP", "RANGE", "ENUM"):
524*e1fe3e4aSElliott Hughes            if self.next_token_ == "ENUM":
525*e1fe3e4aSElliott Hughes                enum = self.parse_enum_()
526*e1fe3e4aSElliott Hughes                coverage.append(enum)
527*e1fe3e4aSElliott Hughes            elif self.next_token_ == "GLYPH":
528*e1fe3e4aSElliott Hughes                self.expect_keyword_("GLYPH")
529*e1fe3e4aSElliott Hughes                name = self.expect_string_()
530*e1fe3e4aSElliott Hughes                coverage.append(ast.GlyphName(name, location=location))
531*e1fe3e4aSElliott Hughes            elif self.next_token_ == "GROUP":
532*e1fe3e4aSElliott Hughes                self.expect_keyword_("GROUP")
533*e1fe3e4aSElliott Hughes                name = self.expect_string_()
534*e1fe3e4aSElliott Hughes                coverage.append(ast.GroupName(name, self, location=location))
535*e1fe3e4aSElliott Hughes            elif self.next_token_ == "RANGE":
536*e1fe3e4aSElliott Hughes                self.expect_keyword_("RANGE")
537*e1fe3e4aSElliott Hughes                start = self.expect_string_()
538*e1fe3e4aSElliott Hughes                self.expect_keyword_("TO")
539*e1fe3e4aSElliott Hughes                end = self.expect_string_()
540*e1fe3e4aSElliott Hughes                coverage.append(ast.Range(start, end, self, location=location))
541*e1fe3e4aSElliott Hughes        return tuple(coverage)
542*e1fe3e4aSElliott Hughes
543*e1fe3e4aSElliott Hughes    def resolve_group(self, group_name):
544*e1fe3e4aSElliott Hughes        return self.groups_.resolve(group_name)
545*e1fe3e4aSElliott Hughes
546*e1fe3e4aSElliott Hughes    def glyph_range(self, start, end):
547*e1fe3e4aSElliott Hughes        return self.glyphs_.range(start, end)
548*e1fe3e4aSElliott Hughes
549*e1fe3e4aSElliott Hughes    def parse_ppem_(self):
550*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
551*e1fe3e4aSElliott Hughes        ppem_name = self.cur_token_
552*e1fe3e4aSElliott Hughes        value = self.expect_number_()
553*e1fe3e4aSElliott Hughes        setting = ast.SettingDefinition(ppem_name, value, location=location)
554*e1fe3e4aSElliott Hughes        return setting
555*e1fe3e4aSElliott Hughes
556*e1fe3e4aSElliott Hughes    def parse_noarg_option_(self):
557*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
558*e1fe3e4aSElliott Hughes        name = self.cur_token_
559*e1fe3e4aSElliott Hughes        value = True
560*e1fe3e4aSElliott Hughes        setting = ast.SettingDefinition(name, value, location=location)
561*e1fe3e4aSElliott Hughes        return setting
562*e1fe3e4aSElliott Hughes
563*e1fe3e4aSElliott Hughes    def parse_cmap_format(self):
564*e1fe3e4aSElliott Hughes        location = self.cur_token_location_
565*e1fe3e4aSElliott Hughes        name = self.cur_token_
566*e1fe3e4aSElliott Hughes        value = (self.expect_number_(), self.expect_number_(), self.expect_number_())
567*e1fe3e4aSElliott Hughes        setting = ast.SettingDefinition(name, value, location=location)
568*e1fe3e4aSElliott Hughes        return setting
569*e1fe3e4aSElliott Hughes
570*e1fe3e4aSElliott Hughes    def is_cur_keyword_(self, k):
571*e1fe3e4aSElliott Hughes        return (self.cur_token_type_ is Lexer.NAME) and (self.cur_token_ == k)
572*e1fe3e4aSElliott Hughes
573*e1fe3e4aSElliott Hughes    def expect_string_(self):
574*e1fe3e4aSElliott Hughes        self.advance_lexer_()
575*e1fe3e4aSElliott Hughes        if self.cur_token_type_ is not Lexer.STRING:
576*e1fe3e4aSElliott Hughes            raise VoltLibError("Expected a string", self.cur_token_location_)
577*e1fe3e4aSElliott Hughes        return self.cur_token_
578*e1fe3e4aSElliott Hughes
579*e1fe3e4aSElliott Hughes    def expect_keyword_(self, keyword):
580*e1fe3e4aSElliott Hughes        self.advance_lexer_()
581*e1fe3e4aSElliott Hughes        if self.cur_token_type_ is Lexer.NAME and self.cur_token_ == keyword:
582*e1fe3e4aSElliott Hughes            return self.cur_token_
583*e1fe3e4aSElliott Hughes        raise VoltLibError('Expected "%s"' % keyword, self.cur_token_location_)
584*e1fe3e4aSElliott Hughes
585*e1fe3e4aSElliott Hughes    def expect_name_(self):
586*e1fe3e4aSElliott Hughes        self.advance_lexer_()
587*e1fe3e4aSElliott Hughes        if self.cur_token_type_ is Lexer.NAME:
588*e1fe3e4aSElliott Hughes            return self.cur_token_
589*e1fe3e4aSElliott Hughes        raise VoltLibError("Expected a name", self.cur_token_location_)
590*e1fe3e4aSElliott Hughes
591*e1fe3e4aSElliott Hughes    def expect_number_(self):
592*e1fe3e4aSElliott Hughes        self.advance_lexer_()
593*e1fe3e4aSElliott Hughes        if self.cur_token_type_ is not Lexer.NUMBER:
594*e1fe3e4aSElliott Hughes            raise VoltLibError("Expected a number", self.cur_token_location_)
595*e1fe3e4aSElliott Hughes        return self.cur_token_
596*e1fe3e4aSElliott Hughes
597*e1fe3e4aSElliott Hughes    def advance_lexer_(self):
598*e1fe3e4aSElliott Hughes        self.cur_token_type_, self.cur_token_, self.cur_token_location_ = (
599*e1fe3e4aSElliott Hughes            self.next_token_type_,
600*e1fe3e4aSElliott Hughes            self.next_token_,
601*e1fe3e4aSElliott Hughes            self.next_token_location_,
602*e1fe3e4aSElliott Hughes        )
603*e1fe3e4aSElliott Hughes        try:
604*e1fe3e4aSElliott Hughes            if self.is_cur_keyword_("END"):
605*e1fe3e4aSElliott Hughes                raise StopIteration
606*e1fe3e4aSElliott Hughes            (
607*e1fe3e4aSElliott Hughes                self.next_token_type_,
608*e1fe3e4aSElliott Hughes                self.next_token_,
609*e1fe3e4aSElliott Hughes                self.next_token_location_,
610*e1fe3e4aSElliott Hughes            ) = self.lexer_.next()
611*e1fe3e4aSElliott Hughes        except StopIteration:
612*e1fe3e4aSElliott Hughes            self.next_token_type_, self.next_token_ = (None, None)
613*e1fe3e4aSElliott Hughes
614*e1fe3e4aSElliott Hughes
615*e1fe3e4aSElliott Hughesclass SymbolTable(object):
616*e1fe3e4aSElliott Hughes    def __init__(self):
617*e1fe3e4aSElliott Hughes        self.scopes_ = [{}]
618*e1fe3e4aSElliott Hughes
619*e1fe3e4aSElliott Hughes    def enter_scope(self):
620*e1fe3e4aSElliott Hughes        self.scopes_.append({})
621*e1fe3e4aSElliott Hughes
622*e1fe3e4aSElliott Hughes    def exit_scope(self):
623*e1fe3e4aSElliott Hughes        self.scopes_.pop()
624*e1fe3e4aSElliott Hughes
625*e1fe3e4aSElliott Hughes    def define(self, name, item):
626*e1fe3e4aSElliott Hughes        self.scopes_[-1][name] = item
627*e1fe3e4aSElliott Hughes
628*e1fe3e4aSElliott Hughes    def resolve(self, name, case_insensitive=True):
629*e1fe3e4aSElliott Hughes        for scope in reversed(self.scopes_):
630*e1fe3e4aSElliott Hughes            item = scope.get(name)
631*e1fe3e4aSElliott Hughes            if item:
632*e1fe3e4aSElliott Hughes                return item
633*e1fe3e4aSElliott Hughes        if case_insensitive:
634*e1fe3e4aSElliott Hughes            for key in scope:
635*e1fe3e4aSElliott Hughes                if key.lower() == name.lower():
636*e1fe3e4aSElliott Hughes                    return scope[key]
637*e1fe3e4aSElliott Hughes        return None
638*e1fe3e4aSElliott Hughes
639*e1fe3e4aSElliott Hughes
640*e1fe3e4aSElliott Hughesclass OrderedSymbolTable(SymbolTable):
641*e1fe3e4aSElliott Hughes    def __init__(self):
642*e1fe3e4aSElliott Hughes        self.scopes_ = [{}]
643*e1fe3e4aSElliott Hughes
644*e1fe3e4aSElliott Hughes    def enter_scope(self):
645*e1fe3e4aSElliott Hughes        self.scopes_.append({})
646*e1fe3e4aSElliott Hughes
647*e1fe3e4aSElliott Hughes    def resolve(self, name, case_insensitive=False):
648*e1fe3e4aSElliott Hughes        SymbolTable.resolve(self, name, case_insensitive=case_insensitive)
649*e1fe3e4aSElliott Hughes
650*e1fe3e4aSElliott Hughes    def range(self, start, end):
651*e1fe3e4aSElliott Hughes        for scope in reversed(self.scopes_):
652*e1fe3e4aSElliott Hughes            if start in scope and end in scope:
653*e1fe3e4aSElliott Hughes                start_idx = list(scope.keys()).index(start)
654*e1fe3e4aSElliott Hughes                end_idx = list(scope.keys()).index(end)
655*e1fe3e4aSElliott Hughes                return list(scope.keys())[start_idx : end_idx + 1]
656*e1fe3e4aSElliott Hughes        return None
657