1#!/usr/bin/env python3
2#
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18# A parser for enum types defined in HIDL.
19# This script can parse HIDL files and generate a parse tree.
20# To use, import and call parse("path/to/file.hal")
21# It will return a Python dictionary with two keys:
22#  - header: an instance of Header
23#  - enums: a dictionary of EnumDecl objects by name
24# This script cannot parse structs for now, but that would be easy to add.
25
26# It requires 'ply' (Python Lex/Yacc).
27
28import ply
29
30tokens = ('package', 'import', 'enum',
31    'COLON', 'IDENTIFIER', 'COMMENT', 'NUMBER', 'HEX', 'OR', 'EQUALS',
32    'LPAREN', 'RPAREN', 'LBRACE', 'RBRACE', 'DOT', 'SEMICOLON', 'VERSION',
33    'COMMA', 'SHIFT')
34
35t_COLON = r':'
36t_NUMBER = r'[0-9]+'
37t_HEX = r'0x[0-9A-Fa-f]+'
38t_OR = r'\|'
39t_EQUALS = r'='
40t_LPAREN = r'\('
41t_RPAREN = r'\)'
42t_SHIFT = r'<<'
43
44def t_COMMENT(t):
45    r'(/\*(.|\n)*?\*/)|(//.*)'
46    pass
47
48t_LBRACE = r'{'
49t_RBRACE = r'}'
50t_DOT = r'\.'
51t_SEMICOLON = r';'
52t_VERSION = r'@[0-9].[0-9]'
53t_COMMA = r','
54t_ignore = ' \n\t'
55
56def t_IDENTIFIER(t):
57    r'[a-zA-Z_][a-zA-Z_0-9]*'
58    if t.value == 'package':
59        t.type = 'package'
60    elif t.value == 'import':
61        t.type = 'import'
62    elif t.value == 'enum':
63        t.type = 'enum'
64    return t
65
66def t_error(t):
67    t.type = t.value[0]
68    t.value = t.value[0]
69    t.lexer.skip(1)
70    return t
71
72import ply.lex as lex
73lexer = lex.lex()
74
75class EnumHeader(object):
76    def __init__(self, name, base):
77        self.name = name
78        self.base = base
79
80    def __str__(self):
81        return '%s%s' % (self.name, ' %s' % self.base if self.base else '')
82
83class EnumDecl(object):
84    def __init__(self, header, cases):
85        self.header = header
86        self.cases = cases
87
88    def __str__(self):
89        return '%s {\n%s\n}' % (self.header,
90            '\n'.join(str(x) for x in self.cases))
91
92    def __repr__(self):
93        return self.__str__()
94
95class EnumCase(object):
96    def __init__(self, name, value):
97        self.name = name
98        self.value = value
99
100    def __str__(self):
101        return '%s = %s' % (self.name, self.value)
102
103class PackageID(object):
104    def __init__(self, name, version):
105        self.name = name
106        self.version = version
107
108    def __str__(self):
109        return '%s%s' % (self.name, self.version)
110
111class Package(object):
112    def __init__(self, package):
113        self.package = package
114
115    def __str__(self):
116        return 'package %s' % self.package
117
118class Import(object):
119    def __init__(self, package):
120        self.package = package
121
122    def __str__(self):
123        return 'import %s' % self.package
124
125class Header(object):
126    def __init__(self, package, imports):
127        self.package = package
128        self.imports = imports
129
130    def __str__(self):
131        return str(self.package) + "\n" + \
132            '\n'.join(str(x) for x in self.imports)
133
134# Error rule for syntax errors
135def p_error(p):
136    print("Syntax error in input: %s" % p)
137
138def p_document(t):
139    'document : header enum_decls'
140    enums = {}
141    for enum in t[2]:
142        enums[enum.header.name] = enum
143    t[0] = {'header' : t[1], 'enums' : enums}
144
145def p_enum_decls_1(t):
146    'enum_decls : enum_decl'
147    t[0] = [t[1]]
148def p_enum_decls_2(t):
149    'enum_decls : enum_decls enum_decl'
150    t[0] = t[1] + [t[2]]
151
152def p_enum_cases_1(t):
153    'enum_cases : enum_case'
154    t[0] = [t[1]]
155def p_enum_cases_2(t):
156    'enum_cases : enum_cases COMMA enum_case'
157    t[0] = t[1] + [t[3]]
158
159def p_enum_base_1(t):
160    'enum_base : VERSION COLON COLON IDENTIFIER'
161    t[0] = '%s::%s' % (t[1], t[4])
162def p_enum_base_2(t):
163    'enum_base : IDENTIFIER'
164    t[0] = t[1]
165
166def p_enum_header_1(t):
167    'enum_header : enum IDENTIFIER'
168    t[0] = EnumHeader(t[2], None)
169def p_enum_header_2(t):
170    'enum_header : enum IDENTIFIER COLON enum_base'
171    t[0] = EnumHeader(t[2], t[4])
172
173def p_enum_decl_1(t):
174    'enum_decl : enum_header LBRACE enum_cases RBRACE SEMICOLON'
175    t[0] = EnumDecl(t[1], t[3])
176def p_enum_decl_2(t):
177    'enum_decl : enum_header LBRACE enum_cases COMMA RBRACE SEMICOLON'
178    t[0] = EnumDecl(t[1], t[3])
179
180def p_enum_value_1(t):
181    '''enum_value : NUMBER
182                  | HEX
183                  | IDENTIFIER'''
184    t[0] = t[1]
185def p_enum_value_2(t):
186    'enum_value : enum_value SHIFT NUMBER'
187    t[0] = '%s << %s' % (t[1], t[3])
188def p_enum_value_3(t):
189    'enum_value : enum_value OR enum_value'
190    t[0] = "%s | %s" % (t[1], t[3])
191def p_enum_value_4(t):
192    'enum_value : LPAREN enum_value RPAREN'
193    t[0] = t[2]
194def p_enum_value_5(t):
195    'enum_value : IDENTIFIER COLON IDENTIFIER'
196    t[0] = '%s:%s' % (t[1],t[3])
197
198def p_enum_case(t):
199    'enum_case : IDENTIFIER EQUALS enum_value'
200    t[0] = EnumCase(t[1], t[3])
201
202def p_header_1(t):
203    'header : package_decl'
204    t[0] = Header(t[1], [])
205
206def p_header_2(t):
207    'header : package_decl import_decls'
208    t[0] = Header(t[1], t[2])
209
210def p_import_decls_1(t):
211    'import_decls : import_decl'
212    t[0] = [t[1]]
213
214def p_import_decls_2(t):
215    'import_decls : import_decls import_decl'
216    t[0] = t[1] + [t[2]]
217
218def p_package_decl(t):
219    'package_decl : package package_ID SEMICOLON'
220    t[0] = Package(t[2])
221
222def p_import_decl(t):
223    'import_decl : import package_ID SEMICOLON'
224    t[0] = Import(t[2])
225
226def p_package_ID(t):
227    'package_ID : dotted_identifier VERSION'
228    t[0] = PackageID(t[1], t[2])
229
230def p_dotted_identifier_1(t):
231    'dotted_identifier : IDENTIFIER'
232    t[0] = t[1]
233def p_dotted_identifier_2(t):
234    'dotted_identifier : dotted_identifier DOT IDENTIFIER'
235    t[0] = t[1] + '.' + t[3]
236
237class SilentLogger(object):
238    def warning(*args):
239        pass
240
241import ply.yacc as yacc
242parser = yacc.yacc(debug=False, write_tables=False, errorlog=SilentLogger())
243import sys
244
245def parse(filename):
246    return parser.parse(open(filename, 'r').read())
247