1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5import os
6from ply import lex, yacc
7
8from ipdl.ast import *
9
10# -----------------------------------------------------------------------------
11
12
13class ParseError(Exception):
14    def __init__(self, loc, fmt, *args):
15        self.loc = loc
16        self.error = (
17            "%s%s: error: %s" % (Parser.includeStackString(), loc, fmt)
18        ) % args
19
20    def __str__(self):
21        return self.error
22
23
24def _safeLinenoValue(t):
25    lineno, value = 0, "???"
26    if hasattr(t, "lineno"):
27        lineno = t.lineno
28    if hasattr(t, "value"):
29        value = t.value
30    return lineno, value
31
32
33def _error(loc, fmt, *args):
34    raise ParseError(loc, fmt, *args)
35
36
37class Parser:
38    # when we reach an |include [protocol] foo;| statement, we need to
39    # save the current parser state and create a new one.  this "stack" is
40    # where that state is saved
41    #
42    # there is one Parser per file
43    current = None
44    parseStack = []
45    parsed = {}
46
47    def __init__(self, type, name, debug=False):
48        assert type and name
49        self.type = type
50        self.debug = debug
51        self.filename = None
52        self.includedirs = None
53        self.loc = None  # not always up to date
54        self.lexer = None
55        self.parser = None
56        self.tu = TranslationUnit(type, name)
57        self.direction = None
58
59    def parse(self, input, filename, includedirs):
60        assert os.path.isabs(filename)
61
62        if self.tu.name in Parser.parsed:
63            priorTU = Parser.parsed[self.tu.name].tu
64            if priorTU.filename != filename:
65                _error(
66                    Loc(filename),
67                    "Trying to load `%s' from a file when we'd already seen it in file `%s'"
68                    % (self.tu.name, priorTU.filename),
69                )
70
71            return priorTU
72
73        self.lexer = lex.lex(debug=self.debug)
74        self.parser = yacc.yacc(debug=self.debug, write_tables=False)
75        self.filename = filename
76        self.includedirs = includedirs
77        self.tu.filename = filename
78
79        Parser.parsed[self.tu.name] = self
80        Parser.parseStack.append(Parser.current)
81        Parser.current = self
82
83        try:
84            ast = self.parser.parse(input=input, lexer=self.lexer, debug=self.debug)
85        finally:
86            Parser.current = Parser.parseStack.pop()
87
88        return ast
89
90    def resolveIncludePath(self, filepath):
91        """Return the absolute path from which the possibly partial
92        |filepath| should be read, or |None| if |filepath| cannot be located."""
93        for incdir in self.includedirs + [""]:
94            realpath = os.path.join(incdir, filepath)
95            if os.path.isfile(realpath):
96                return os.path.abspath(realpath)
97        return None
98
99    # returns a GCC-style string representation of the include stack.
100    # e.g.,
101    #   in file included from 'foo.ipdl', line 120:
102    #   in file included from 'bar.ipd', line 12:
103    # which can be printed above a proper error message or warning
104    @staticmethod
105    def includeStackString():
106        s = ""
107        for parse in Parser.parseStack[1:]:
108            s += "  in file included from `%s', line %d:\n" % (
109                parse.loc.filename,
110                parse.loc.lineno,
111            )
112        return s
113
114
115def locFromTok(p, num):
116    return Loc(Parser.current.filename, p.lineno(num))
117
118
119# -----------------------------------------------------------------------------
120
121reserved = set(
122    (
123        "async",
124        "both",
125        "child",
126        "class",
127        "from",
128        "include",
129        "intr",
130        "manager",
131        "manages",
132        "namespace",
133        "nested",
134        "nullable",
135        "or",
136        "parent",
137        "protocol",
138        "refcounted",
139        "returns",
140        "struct",
141        "sync",
142        "union",
143        "UniquePtr",
144        "upto",
145        "using",
146    )
147)
148tokens = [
149    "COLONCOLON",
150    "ID",
151    "STRING",
152] + [r.upper() for r in reserved]
153
154t_COLONCOLON = "::"
155
156literals = "(){}[]<>;:,?="
157t_ignore = " \f\t\v"
158
159
160def t_linecomment(t):
161    r"//[^\n]*"
162
163
164def t_multilinecomment(t):
165    r"/\*(\n|.)*?\*/"
166    t.lexer.lineno += t.value.count("\n")
167
168
169def t_NL(t):
170    r"(?:\r\n|\n|\n)+"
171    t.lexer.lineno += len(t.value)
172
173
174def t_ID(t):
175    r"[a-zA-Z_][a-zA-Z0-9_]*"
176    if t.value in reserved:
177        t.type = t.value.upper()
178    return t
179
180
181def t_STRING(t):
182    r'"[^"\n]*"'
183    t.value = t.value[1:-1]
184    return t
185
186
187def t_error(t):
188    _error(
189        Loc(Parser.current.filename, t.lineno),
190        "lexically invalid characters `%s",
191        t.value,
192    )
193
194
195# -----------------------------------------------------------------------------
196
197
198def p_TranslationUnit(p):
199    """TranslationUnit : Preamble NamespacedStuff"""
200    tu = Parser.current.tu
201    tu.loc = Loc(tu.filename)
202    for stmt in p[1]:
203        if isinstance(stmt, CxxInclude):
204            tu.addCxxInclude(stmt)
205        elif isinstance(stmt, Include):
206            tu.addInclude(stmt)
207        elif isinstance(stmt, UsingStmt):
208            tu.addUsingStmt(stmt)
209        else:
210            assert 0
211
212    for thing in p[2]:
213        if isinstance(thing, StructDecl):
214            tu.addStructDecl(thing)
215        elif isinstance(thing, UnionDecl):
216            tu.addUnionDecl(thing)
217        elif isinstance(thing, Protocol):
218            if tu.protocol is not None:
219                _error(thing.loc, "only one protocol definition per file")
220            tu.protocol = thing
221        else:
222            assert 0
223
224    # The "canonical" namespace of the tu, what it's considered to be
225    # in for the purposes of C++: |#include "foo/bar/TU.h"|
226    if tu.protocol:
227        assert tu.filetype == "protocol"
228        tu.namespaces = tu.protocol.namespaces
229        tu.name = tu.protocol.name
230    else:
231        assert tu.filetype == "header"
232        # There's not really a canonical "thing" in headers.  So
233        # somewhat arbitrarily use the namespace of the last
234        # interesting thing that was declared.
235        for thing in reversed(tu.structsAndUnions):
236            tu.namespaces = thing.namespaces
237            break
238
239    p[0] = tu
240
241
242# --------------------
243# Preamble
244
245
246def p_Preamble(p):
247    """Preamble : Preamble PreambleStmt ';'
248    |"""
249    if 1 == len(p):
250        p[0] = []
251    else:
252        p[1].append(p[2])
253        p[0] = p[1]
254
255
256def p_PreambleStmt(p):
257    """PreambleStmt : CxxIncludeStmt
258    | IncludeStmt
259    | UsingStmt"""
260    p[0] = p[1]
261
262
263def p_CxxIncludeStmt(p):
264    """CxxIncludeStmt : INCLUDE STRING"""
265    p[0] = CxxInclude(locFromTok(p, 1), p[2])
266
267
268def p_IncludeStmt(p):
269    """IncludeStmt : INCLUDE PROTOCOL ID
270    | INCLUDE ID"""
271    loc = locFromTok(p, 1)
272
273    Parser.current.loc = loc
274    if 4 == len(p):
275        id = p[3]
276        type = "protocol"
277    else:
278        id = p[2]
279        type = "header"
280    inc = Include(loc, type, id)
281
282    path = Parser.current.resolveIncludePath(inc.file)
283    if path is None:
284        raise ParseError(loc, "can't locate include file `%s'" % (inc.file))
285
286    inc.tu = Parser(type, id).parse(open(path).read(), path, Parser.current.includedirs)
287    p[0] = inc
288
289
290def p_UsingKind(p):
291    """UsingKind : CLASS
292    | STRUCT
293    |"""
294    p[0] = p[1] if 2 == len(p) else None
295
296
297def p_MaybeRefcounted(p):
298    """MaybeRefcounted : REFCOUNTED
299    |"""
300    p[0] = 2 == len(p)
301
302
303def p_UsingStmt(p):
304    """UsingStmt : Attributes USING UsingKind CxxType FROM STRING"""
305    p[0] = UsingStmt(
306        locFromTok(p, 2),
307        attributes=p[1],
308        kind=p[3],
309        cxxTypeSpec=p[4],
310        cxxHeader=p[6],
311    )
312
313
314# --------------------
315# Namespaced stuff
316
317
318def p_NamespacedStuff(p):
319    """NamespacedStuff : NamespacedStuff NamespaceThing
320    | NamespaceThing"""
321    if 2 == len(p):
322        p[0] = p[1]
323    else:
324        p[1].extend(p[2])
325        p[0] = p[1]
326
327
328def p_NamespaceThing(p):
329    """NamespaceThing : NAMESPACE ID '{' NamespacedStuff '}'
330    | StructDecl
331    | UnionDecl
332    | ProtocolDefn"""
333    if 2 == len(p):
334        p[0] = [p[1]]
335    else:
336        for thing in p[4]:
337            thing.addOuterNamespace(Namespace(locFromTok(p, 1), p[2]))
338        p[0] = p[4]
339
340
341def p_StructDecl(p):
342    """StructDecl : Attributes STRUCT ID '{' StructFields '}' ';'
343    | Attributes STRUCT ID '{' '}' ';'"""
344    if 8 == len(p):
345        p[0] = StructDecl(locFromTok(p, 2), p[3], p[5], p[1])
346    else:
347        p[0] = StructDecl(locFromTok(p, 2), p[3], [], p[1])
348
349
350def p_StructFields(p):
351    """StructFields : StructFields StructField ';'
352    | StructField ';'"""
353    if 3 == len(p):
354        p[0] = [p[1]]
355    else:
356        p[1].append(p[2])
357        p[0] = p[1]
358
359
360def p_StructField(p):
361    """StructField : Type ID"""
362    p[0] = StructField(locFromTok(p, 1), p[1], p[2])
363
364
365def p_UnionDecl(p):
366    """UnionDecl : Attributes UNION ID '{' ComponentTypes  '}' ';'"""
367    p[0] = UnionDecl(locFromTok(p, 2), p[3], p[5], p[1])
368
369
370def p_ComponentTypes(p):
371    """ComponentTypes : ComponentTypes Type ';'
372    | Type ';'"""
373    if 3 == len(p):
374        p[0] = [p[1]]
375    else:
376        p[1].append(p[2])
377        p[0] = p[1]
378
379
380def p_ProtocolDefn(p):
381    """ProtocolDefn : OptionalProtocolSendSemanticsQual MaybeRefcounted \
382                      PROTOCOL ID '{' ProtocolBody '}' ';'"""
383    protocol = p[6]
384    protocol.loc = locFromTok(p, 3)
385    protocol.name = p[4]
386    protocol.nested = p[1][0]
387    protocol.sendSemantics = p[1][1]
388    protocol.refcounted = p[2]
389    p[0] = protocol
390
391    if Parser.current.type == "header":
392        _error(
393            protocol.loc,
394            "can't define a protocol in a header.  Do it in a protocol spec instead.",
395        )
396
397
398def p_ProtocolBody(p):
399    """ProtocolBody : ManagersStmtOpt"""
400    p[0] = p[1]
401
402
403# --------------------
404# manager/manages stmts
405
406
407def p_ManagersStmtOpt(p):
408    """ManagersStmtOpt : ManagersStmt ManagesStmtsOpt
409    | ManagesStmtsOpt"""
410    if 2 == len(p):
411        p[0] = p[1]
412    else:
413        p[2].managers = p[1]
414        p[0] = p[2]
415
416
417def p_ManagersStmt(p):
418    """ManagersStmt : MANAGER ManagerList ';'"""
419    if 1 == len(p):
420        p[0] = []
421    else:
422        p[0] = p[2]
423
424
425def p_ManagerList(p):
426    """ManagerList : ID
427    | ManagerList OR ID"""
428    if 2 == len(p):
429        p[0] = [Manager(locFromTok(p, 1), p[1])]
430    else:
431        p[1].append(Manager(locFromTok(p, 3), p[3]))
432        p[0] = p[1]
433
434
435def p_ManagesStmtsOpt(p):
436    """ManagesStmtsOpt : ManagesStmt ManagesStmtsOpt
437    | MessageDeclsOpt"""
438    if 2 == len(p):
439        p[0] = p[1]
440    else:
441        p[2].managesStmts.insert(0, p[1])
442        p[0] = p[2]
443
444
445def p_ManagesStmt(p):
446    """ManagesStmt : MANAGES ID ';'"""
447    p[0] = ManagesStmt(locFromTok(p, 1), p[2])
448
449
450# --------------------
451# Message decls
452
453
454def p_MessageDeclsOpt(p):
455    """MessageDeclsOpt : MessageDeclThing MessageDeclsOpt
456    |"""
457    if 1 == len(p):
458        # we fill in |loc| in the Protocol rule
459        p[0] = Protocol(None)
460    else:
461        p[2].messageDecls.insert(0, p[1])
462        p[0] = p[2]
463
464
465def p_MessageDeclThing(p):
466    """MessageDeclThing : MessageDirectionLabel ':' MessageDecl ';'
467    | MessageDecl ';'"""
468    if 3 == len(p):
469        p[0] = p[1]
470    else:
471        p[0] = p[3]
472
473
474def p_MessageDirectionLabel(p):
475    """MessageDirectionLabel : PARENT
476    | CHILD
477    | BOTH"""
478    if p[1] == "parent":
479        Parser.current.direction = IN
480    elif p[1] == "child":
481        Parser.current.direction = OUT
482    elif p[1] == "both":
483        Parser.current.direction = INOUT
484    else:
485        assert 0
486
487
488def p_MessageDecl(p):
489    """MessageDecl : Attributes SendSemantics MessageBody"""
490    msg = p[3]
491    msg.attributes = p[1]
492    msg.sendSemantics = p[2]
493
494    if Parser.current.direction is None:
495        _error(msg.loc, "missing message direction")
496    msg.direction = Parser.current.direction
497
498    p[0] = msg
499
500
501def p_MessageBody(p):
502    """MessageBody : ID MessageInParams MessageOutParams"""
503    # FIXME/cjones: need better loc info: use one of the quals
504    name = p[1]
505    msg = MessageDecl(locFromTok(p, 1))
506    msg.name = name
507    msg.addInParams(p[2])
508    msg.addOutParams(p[3])
509
510    p[0] = msg
511
512
513def p_MessageInParams(p):
514    """MessageInParams : '(' ParamList ')'"""
515    p[0] = p[2]
516
517
518def p_MessageOutParams(p):
519    """MessageOutParams : RETURNS '(' ParamList ')'
520    |"""
521    if 1 == len(p):
522        p[0] = []
523    else:
524        p[0] = p[3]
525
526
527# --------------------
528# Attributes
529def p_Attributes(p):
530    """Attributes : '[' AttributeList ']'
531    |"""
532    p[0] = {}
533    if 4 == len(p):
534        for attr in p[2]:
535            if attr.name in p[0]:
536                _error(attr.loc, "Repeated extended attribute `%s'", attr.name)
537            p[0][attr.name] = attr
538
539
540def p_AttributeList(p):
541    """AttributeList : Attribute ',' AttributeList
542    | Attribute"""
543    p[0] = [p[1]]
544    if 4 == len(p):
545        p[0] += p[3]
546
547
548def p_Attribute(p):
549    """Attribute : ID AttributeValue"""
550    p[0] = Attribute(locFromTok(p, 1), p[1], p[2])
551
552
553def p_AttributeValue(p):
554    """AttributeValue : '=' ID
555    |"""
556    if 1 == len(p):
557        p[0] = None
558    else:
559        p[0] = p[2]
560
561
562# --------------------
563# Minor stuff
564def p_Nested(p):
565    """Nested : ID"""
566    kinds = {"not": 1, "inside_sync": 2, "inside_cpow": 3}
567    if p[1] not in kinds:
568        _error(
569            locFromTok(p, 1), "Expected not, inside_sync, or inside_cpow for nested()"
570        )
571
572    p[0] = {"nested": kinds[p[1]]}
573
574
575def p_SendSemantics(p):
576    """SendSemantics : ASYNC
577    | SYNC
578    | INTR"""
579    if p[1] == "async":
580        p[0] = ASYNC
581    elif p[1] == "sync":
582        p[0] = SYNC
583    else:
584        assert p[1] == "intr"
585        p[0] = INTR
586
587
588def p_OptionalProtocolSendSemanticsQual(p):
589    """OptionalProtocolSendSemanticsQual : ProtocolSendSemanticsQual
590    |"""
591    if 2 == len(p):
592        p[0] = p[1]
593    else:
594        p[0] = [NOT_NESTED, ASYNC]
595
596
597def p_ProtocolSendSemanticsQual(p):
598    """ProtocolSendSemanticsQual : ASYNC
599    | SYNC
600    | NESTED '(' UPTO Nested ')' ASYNC
601    | NESTED '(' UPTO Nested ')' SYNC
602    | INTR"""
603    if p[1] == "nested":
604        mtype = p[6]
605        nested = p[4]
606    else:
607        mtype = p[1]
608        nested = NOT_NESTED
609
610    if mtype == "async":
611        mtype = ASYNC
612    elif mtype == "sync":
613        mtype = SYNC
614    elif mtype == "intr":
615        mtype = INTR
616    else:
617        assert 0
618
619    p[0] = [nested, mtype]
620
621
622def p_ParamList(p):
623    """ParamList : ParamList ',' Param
624    | Param
625    |"""
626    if 1 == len(p):
627        p[0] = []
628    elif 2 == len(p):
629        p[0] = [p[1]]
630    else:
631        p[1].append(p[3])
632        p[0] = p[1]
633
634
635def p_Param(p):
636    """Param : Attributes Type ID"""
637    p[0] = Param(locFromTok(p, 2), p[2], p[3], p[1])
638
639
640def p_Type(p):
641    """Type : MaybeNullable BasicType"""
642    # only actor types are nullable; we check this in the type checker
643    p[2].nullable = p[1]
644    p[0] = p[2]
645
646
647def p_BasicType(p):
648    """BasicType : CxxID
649    | CxxID '[' ']'
650    | CxxID '?'
651    | CxxUniquePtrInst"""
652    # ID == CxxType; we forbid qnames here,
653    # in favor of the |using| declaration
654    if not isinstance(p[1], TypeSpec):
655        assert (len(p[1]) == 2) or (len(p[1]) == 3)
656        if 2 == len(p[1]):
657            # p[1] is CxxID. isunique = False
658            p[1] = p[1] + (False,)
659        loc, id, isunique = p[1]
660        p[1] = TypeSpec(loc, QualifiedId(loc, id))
661        p[1].uniqueptr = isunique
662    if 4 == len(p):
663        p[1].array = True
664    if 3 == len(p):
665        p[1].maybe = True
666    p[0] = p[1]
667
668
669def p_MaybeNullable(p):
670    """MaybeNullable : NULLABLE
671    |"""
672    p[0] = 2 == len(p)
673
674
675# --------------------
676# C++ stuff
677
678
679def p_CxxType(p):
680    """CxxType : QualifiedID
681    | CxxID"""
682    if isinstance(p[1], QualifiedId):
683        p[0] = TypeSpec(p[1].loc, p[1])
684    else:
685        loc, id = p[1]
686        p[0] = TypeSpec(loc, QualifiedId(loc, id))
687
688
689def p_QualifiedID(p):
690    """QualifiedID : QualifiedID COLONCOLON CxxID
691    | CxxID COLONCOLON CxxID"""
692    if isinstance(p[1], QualifiedId):
693        loc, id = p[3]
694        p[1].qualify(id)
695        p[0] = p[1]
696    else:
697        loc1, id1 = p[1]
698        _, id2 = p[3]
699        p[0] = QualifiedId(loc1, id2, [id1])
700
701
702def p_CxxID(p):
703    """CxxID : ID
704    | CxxTemplateInst"""
705    if isinstance(p[1], tuple):
706        p[0] = p[1]
707    else:
708        p[0] = (locFromTok(p, 1), str(p[1]))
709
710
711def p_CxxTemplateInst(p):
712    """CxxTemplateInst : ID '<' ID '>'"""
713    p[0] = (locFromTok(p, 1), str(p[1]) + "<" + str(p[3]) + ">")
714
715
716def p_CxxUniquePtrInst(p):
717    """CxxUniquePtrInst : UNIQUEPTR '<' ID '>'"""
718    p[0] = (locFromTok(p, 1), str(p[3]), True)
719
720
721def p_error(t):
722    lineno, value = _safeLinenoValue(t)
723    _error(Loc(Parser.current.filename, lineno), "bad syntax near `%s'", value)
724