xref: /aosp_15_r20/external/angle/src/common/spirv/gen_spirv_builder_and_parser.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#!/usr/bin/python3
2# Copyright 2021 The ANGLE Project Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# gen_spirv_builder_and_parser.py:
7#   Code generation for SPIR-V instruction builder and parser.
8#   NOTE: don't run this script directly. Run scripts/run_code_generation.py.
9
10import itertools
11import json
12import os
13import sys
14
15# ANGLE uses instructions from SPIR-V 1.0 mostly, but also OpCopyLogical from SPIR-V 1.4.
16SPIRV_GRAMMAR_FILE = '../../../third_party/spirv-headers/src/include/spirv/unified1/spirv.core.grammar.json'
17
18# The script has two sets of outputs, a header and source file for SPIR-V code generation, and a
19# header and source file for SPIR-V parsing.
20SPIRV_BUILDER_FILE = 'spirv_instruction_builder'
21SPIRV_PARSER_FILE = 'spirv_instruction_parser'
22
23# The types are either defined in spirv_types.h (to use strong types), or are enums that are
24# defined by SPIR-V headers.
25ANGLE_DEFINED_TYPES = [
26    'IdRef', 'IdResult', 'IdResultType', 'IdMemorySemantics', 'IdScope', 'LiteralInteger',
27    'LiteralString', 'LiteralContextDependentNumber', 'LiteralExtInstInteger',
28    'PairLiteralIntegerIdRef', 'PairIdRefLiteralInteger', 'PairIdRefIdRef'
29]
30
31HEADER_TEMPLATE = """// GENERATED FILE - DO NOT EDIT.
32// Generated by {script_name} using data from {data_source_name}.
33//
34// Copyright 2021 The ANGLE Project Authors. All rights reserved.
35// Use of this source code is governed by a BSD-style license that can be
36// found in the LICENSE file.
37//
38// {file_name}_autogen.h:
39//   Functions to {verb} SPIR-V binary for each instruction.
40
41#ifndef COMMON_SPIRV_{file_name_capitalized}AUTOGEN_H_
42#define COMMON_SPIRV_{file_name_capitalized}AUTOGEN_H_
43
44#include <spirv/unified1/spirv.hpp>
45
46#include "spirv_types.h"
47
48namespace angle
49{{
50namespace spirv
51{{
52{prototype_list}
53}}  // namespace spirv
54}}  // namespace angle
55
56#endif // COMMON_SPIRV_{file_name_capitalized}AUTOGEN_H_
57"""
58
59SOURCE_TEMPLATE = """// GENERATED FILE - DO NOT EDIT.
60// Generated by {script_name} using data from {data_source_name}.
61//
62// Copyright 2021 The ANGLE Project Authors. All rights reserved.
63// Use of this source code is governed by a BSD-style license that can be
64// found in the LICENSE file.
65//
66// {file_name}_autogen.cpp:
67//   Functions to {verb} SPIR-V binary for each instruction.
68
69#include "{file_name}_autogen.h"
70
71#include <string.h>
72
73#include "common/debug.h"
74
75namespace angle
76{{
77namespace spirv
78{{
79{helper_functions}
80
81{function_list}
82}}  // namespace spirv
83}}  // namespace angle
84"""
85
86BUILDER_HELPER_FUNCTIONS = """namespace
87{
88uint32_t MakeLengthOp(size_t length, spv::Op op)
89{
90    ASSERT(length <= 0xFFFFu);
91    ASSERT(op <= 0xFFFFu);
92
93    // It's easy for a complex shader to be crafted to hit the length limit,
94    // turn that into a crash instead of a security bug.  Ideally, the compiler
95    // would gracefully fail compilation, so this is more of a safety net.
96    if (ANGLE_UNLIKELY(length > 0xFFFFu))
97    {
98        ERR() << "Complex shader not representible in SPIR-V";
99        ANGLE_CRASH();
100    }
101
102    return static_cast<uint32_t>(length) << 16 | op;
103}
104}  // anonymous namespace
105
106void WriteSpirvHeader(std::vector<uint32_t> *blob, uint32_t version, uint32_t idCount)
107{
108    // Header:
109    //
110    //  - Magic number
111    //  - Version (1.X)
112    //  - ANGLE's Generator number:
113    //     * 24 for tool id (higher 16 bits)
114    //     * 1 for tool version (lower 16 bits))
115    //  - Bound (idCount)
116    //  - 0 (reserved)
117    constexpr uint32_t kANGLEGeneratorId = 24;
118    constexpr uint32_t kANGLEGeneratorVersion = 1;
119
120    ASSERT(blob->empty());
121
122    blob->push_back(spv::MagicNumber);
123    blob->push_back(version);
124    blob->push_back(kANGLEGeneratorId << 16 | kANGLEGeneratorVersion);
125    blob->push_back(idCount);
126    blob->push_back(0x00000000);
127}
128"""
129
130BUILDER_HELPER_FUNCTION_PROTOTYPE = """
131    void WriteSpirvHeader(std::vector<uint32_t> *blob, uint32_t version, uint32_t idCount);
132"""
133
134PARSER_FIXED_FUNCTIONS_PROTOTYPES = """void GetInstructionOpAndLength(const uint32_t *_instruction,
135    spv::Op *opOut, uint32_t *lengthOut);
136"""
137
138PARSER_FIXED_FUNCTIONS = """void GetInstructionOpAndLength(const uint32_t *_instruction,
139    spv::Op *opOut, uint32_t *lengthOut)
140{
141    constexpr uint32_t kOpMask = 0xFFFFu;
142    *opOut = static_cast<spv::Op>(_instruction[0] & kOpMask);
143    *lengthOut = _instruction[0] >> 16;
144}
145"""
146
147TEMPLATE_BUILDER_FUNCTION_PROTOTYPE = """void Write{op}(Blob *blob {param_list})"""
148TEMPLATE_BUILDER_FUNCTION_BODY = """{{
149    const size_t startSize = blob->size();
150    blob->push_back(0);
151    {push_back_lines}
152    (*blob)[startSize] = MakeLengthOp(blob->size() - startSize, spv::Op{op});
153}}
154"""
155
156TEMPLATE_PARSER_FUNCTION_PROTOTYPE = """void Parse{op}(const uint32_t *_instruction {param_list})"""
157TEMPLATE_PARSER_FUNCTION_BODY = """{{
158    spv::Op _op;
159    uint32_t _length;
160    GetInstructionOpAndLength(_instruction, &_op, &_length);
161    ASSERT(_op == spv::Op{op});
162    uint32_t _o = 1;
163    {parse_lines}
164}}
165"""
166
167
168def load_grammar(grammar_file):
169    with open(grammar_file) as grammar_in:
170        grammar = json.loads(grammar_in.read())
171
172    return grammar
173
174
175def remove_chars(string, chars):
176    return ''.join(list(filter(lambda c: c not in chars, string)))
177
178
179def make_camel_case(name):
180    return name[0].lower() + name[1:]
181
182
183class Writer:
184
185    def __init__(self):
186        self.path_prefix = os.path.dirname(os.path.realpath(__file__)) + os.path.sep
187        self.grammar = load_grammar(self.path_prefix + SPIRV_GRAMMAR_FILE)
188
189        # List of extensions needed by ANGLE
190        cherry_picked_extensions = {'SPV_EXT_fragment_shader_interlock'}
191
192        # If an instruction has a parameter of these types, the instruction is ignored
193        self.unsupported_kinds = set(['LiteralSpecConstantOpInteger'])
194        # If an instruction requires a capability of these kinds, the instruction is ignored
195        self.unsupported_capabilities = set(['Kernel', 'Addresses'])
196        # If an instruction requires an extension other than these, the instruction is ignored
197        self.supported_extensions = set([]) | cherry_picked_extensions
198        # List of bit masks.  These have 'Mask' added to their typename in SPIR-V headers.
199        self.bit_mask_types = set([])
200
201        # List of generated instructions builder/parser functions so far.
202        self.instruction_builder_prototypes = [BUILDER_HELPER_FUNCTION_PROTOTYPE]
203        self.instruction_builder_impl = []
204        self.instruction_parser_prototypes = [PARSER_FIXED_FUNCTIONS_PROTOTYPES]
205        self.instruction_parser_impl = [PARSER_FIXED_FUNCTIONS]
206
207    def write_builder_and_parser(self):
208        """Generates four files, a set of header and source files for generating SPIR-V instructions
209        and a set for parsing SPIR-V instructions.  Only Vulkan instructions are processed (and not
210        OpenCL for example), and some assumptions are made based on ANGLE's usage (for example that
211        constants always fit in one 32-bit unit, as GLES doesn't support double or 64-bit types).
212
213        Additionally, enums and other parameter 'kinds' are not parsed from the json file, but
214        rather use the definitions from the SPIR-V headers repository and the spirv_types.h file."""
215
216        # Recurse through capabilities and accumulate ones that depend on unsupported ones.
217        self.accumulate_unsupported_capabilities()
218
219        self.find_bit_mask_types()
220
221        for instruction in self.grammar['instructions']:
222            self.generate_instruction_functions(instruction)
223
224        # Write out the files.
225        data_source_base_name = os.path.basename(SPIRV_GRAMMAR_FILE)
226        builder_template_args = {
227            'script_name': os.path.basename(sys.argv[0]),
228            'data_source_name': data_source_base_name,
229            'file_name': SPIRV_BUILDER_FILE,
230            'file_name_capitalized': remove_chars(SPIRV_BUILDER_FILE.upper(), '_'),
231            'verb': 'generate',
232            'helper_functions': BUILDER_HELPER_FUNCTIONS,
233            'prototype_list': ''.join(self.instruction_builder_prototypes),
234            'function_list': ''.join(self.instruction_builder_impl)
235        }
236        parser_template_args = {
237            'script_name': os.path.basename(sys.argv[0]),
238            'data_source_name': data_source_base_name,
239            'file_name': SPIRV_PARSER_FILE,
240            'file_name_capitalized': remove_chars(SPIRV_PARSER_FILE.upper(), '_'),
241            'verb': 'parse',
242            'helper_functions': '',
243            'prototype_list': ''.join(self.instruction_parser_prototypes),
244            'function_list': ''.join(self.instruction_parser_impl)
245        }
246
247        with (open(self.path_prefix + SPIRV_BUILDER_FILE + '_autogen.h', 'w')) as f:
248            f.write(HEADER_TEMPLATE.format(**builder_template_args))
249
250        with (open(self.path_prefix + SPIRV_BUILDER_FILE + '_autogen.cpp', 'w')) as f:
251            f.write(SOURCE_TEMPLATE.format(**builder_template_args))
252
253        with (open(self.path_prefix + SPIRV_PARSER_FILE + '_autogen.h', 'w')) as f:
254            f.write(HEADER_TEMPLATE.format(**parser_template_args))
255
256        with (open(self.path_prefix + SPIRV_PARSER_FILE + '_autogen.cpp', 'w')) as f:
257            f.write(SOURCE_TEMPLATE.format(**parser_template_args))
258
259    def requires_unsupported_capability(self, item):
260        depends = item.get('capabilities', [])
261        if len(depends) == 0:
262            return False
263        return all([dep in self.unsupported_capabilities for dep in depends])
264
265    def requires_unsupported_extension(self, item):
266        extensions = item.get('extensions', [])
267        return any([ext not in self.supported_extensions for ext in extensions])
268
269    def accumulate_unsupported_capabilities(self):
270        operand_kinds = self.grammar['operand_kinds']
271
272        # Find the Capability enum
273        for kind in filter(lambda entry: entry['kind'] == 'Capability', operand_kinds):
274            capabilities = kind['enumerants']
275            for capability in capabilities:
276                name = capability['enumerant']
277                # For each capability, see if any of the capabilities they depend on is unsupported.
278                # If so, add the capability to the list of unsupported ones.
279                if self.requires_unsupported_capability(capability):
280                    self.unsupported_capabilities.add(name)
281                    continue
282                # Do the same for extensions
283                if self.requires_unsupported_extension(capability):
284                    self.unsupported_capabilities.add(name)
285
286    def find_bit_mask_types(self):
287        operand_kinds = self.grammar['operand_kinds']
288
289        # Find the BitEnum categories
290        for bitEnumEntry in filter(lambda entry: entry['category'] == 'BitEnum', operand_kinds):
291            self.bit_mask_types.add(bitEnumEntry['kind'])
292
293    def get_operand_name(self, operand, operand_distinguisher):
294        kind = operand['kind']
295        name = operand.get('name')
296
297        # If no name is given, derive the name from the kind
298        if name is None:
299            assert (kind.find(' ') == -1)
300            return make_camel_case(kind) + str(operand_distinguisher)
301
302        quantifier = operand.get('quantifier', '')
303        name = remove_chars(name, "'")
304
305        # First, a number of special-cases for optional lists
306        if quantifier == '*':
307            suffix = 'List'
308
309            # For IdRefs, change 'Xyz 1', +\n'Xyz 2', +\n...' to xyzList
310            if kind == 'IdRef':
311                if name.find(' ') != -1:
312                    name = name[0:name.find(' ')]
313
314            # Otherwise, if it's a pair in the form of 'Xyz, Abc, ...', change it to xyzAbcPairList
315            elif kind.startswith('Pair'):
316                suffix = 'PairList'
317
318            # Otherwise, it's just a list, so change `xyz abc` to `xyzAbcList
319
320            name = remove_chars(name, " ,.")
321            return make_camel_case(name) + suffix
322
323        # Otherwise, remove invalid characters and make the first letter lower case.
324        name = remove_chars(name, " .,+\n~")
325
326        name = make_camel_case(name)
327
328        # Make sure the name is not a C++ keyword
329        return 'default_' if name == 'default' else name
330
331    def get_operand_namespace(self, kind):
332        return '' if kind in ANGLE_DEFINED_TYPES else 'spv::'
333
334    def get_operand_type_suffix(self, kind):
335        return 'Mask' if kind in self.bit_mask_types else ''
336
337    def get_kind_cpp_type(self, kind):
338        return self.get_operand_namespace(kind) + kind + self.get_operand_type_suffix(kind)
339
340    def get_operand_type_in_and_out(self, operand):
341        kind = operand['kind']
342        quantifier = operand.get('quantifier', '')
343
344        is_array = quantifier == '*'
345        is_optional = quantifier == '?'
346        cpp_type = self.get_kind_cpp_type(kind)
347
348        if is_array:
349            type_in = 'const ' + cpp_type + 'List &'
350            type_out = cpp_type + 'List *'
351        elif is_optional:
352            type_in = 'const ' + cpp_type + ' *'
353            type_out = cpp_type + ' *'
354        else:
355            type_in = cpp_type
356            type_out = cpp_type + ' *'
357
358        return (type_in, type_out, is_array, is_optional)
359
360    def get_operand_push_back_line(self, operand, operand_name, is_array, is_optional):
361        kind = operand['kind']
362        pre = ''
363        post = ''
364        accessor = '.'
365        item = operand_name
366        item_dereferenced = item
367        if is_optional:
368            # If optional, surround with an if.
369            pre = 'if (' + operand_name + ') {\n'
370            post = '\n}'
371            accessor = '->'
372            item_dereferenced = '*' + item
373        elif is_array:
374            # If array, surround with a loop.
375            pre = 'for (const auto &operand : ' + operand_name + ') {\n'
376            post = '\n}'
377            item = 'operand'
378            item_dereferenced = item
379
380        # Usually the operand is one uint32_t, but it may be pair.  Handle the pairs especially.
381        if kind == 'PairLiteralIntegerIdRef':
382            line = 'blob->push_back(' + item + accessor + 'literal);'
383            line += 'blob->push_back(' + item + accessor + 'id);'
384        elif kind == 'PairIdRefLiteralInteger':
385            line = 'blob->push_back(' + item + accessor + 'id);'
386            line += 'blob->push_back(' + item + accessor + 'literal);'
387        elif kind == 'PairIdRefIdRef':
388            line = 'blob->push_back(' + item + accessor + 'id1);'
389            line += 'blob->push_back(' + item + accessor + 'id2);'
390        elif kind == 'LiteralString':
391            line = '{size_t d = blob->size();'
392            line += 'blob->resize(d + strlen(' + item_dereferenced + ') / 4 + 1, 0);'
393            # We currently don't have any big-endian devices in the list of supported platforms.
394            # Literal strings in SPIR-V are stored little-endian (SPIR-V 1.0 Section 2.2.1, Literal
395            # String), so if a big-endian device is to be supported, the string copy here should
396            # be adjusted.
397            line += 'ASSERT(IsLittleEndian());'
398            line += 'strcpy(reinterpret_cast<char *>(blob->data() + d), ' + item_dereferenced + ');}'
399        else:
400            line = 'blob->push_back(' + item_dereferenced + ');'
401
402        return pre + line + post
403
404    def get_operand_parse_line(self, operand, operand_name, is_array, is_optional):
405        kind = operand['kind']
406        pre = ''
407        post = ''
408        accessor = '->'
409
410        if is_optional:
411            # If optional, surround with an if, both checking if argument is provided, and whether
412            # it exists.
413            pre = 'if (' + operand_name + ' && _o < _length) {\n'
414            post = '\n}'
415        elif is_array:
416            # If array, surround with an if and a loop.
417            pre = 'if (' + operand_name + ') {\n'
418            pre += 'while (_o < _length) {\n'
419            post = '\n}}'
420            accessor = '.'
421
422        # Usually the operand is one uint32_t, but it may be pair.  Handle the pairs especially.
423        if kind == 'PairLiteralIntegerIdRef':
424            if is_array:
425                line = operand_name + '->emplace_back(' + kind + '{LiteralInteger(_instruction[_o]), IdRef(_instruction[_o + 1])});'
426                line += '_o += 2;'
427            else:
428                line = operand_name + '->literal = LiteralInteger(_instruction[_o++]);'
429                line += operand_name + '->id = IdRef(_instruction[_o++]);'
430        elif kind == 'PairIdRefLiteralInteger':
431            if is_array:
432                line = operand_name + '->emplace_back(' + kind + '{IdRef(_instruction[_o]), LiteralInteger(_instruction[_o + 1])});'
433                line += '_o += 2;'
434            else:
435                line = operand_name + '->id = IdRef(_instruction[_o++]);'
436                line += operand_name + '->literal = LiteralInteger(_instruction[_o++]);'
437        elif kind == 'PairIdRefIdRef':
438            if is_array:
439                line = operand_name + '->emplace_back(' + kind + '{IdRef(_instruction[_o]), IdRef(_instruction[_o + 1])});'
440                line += '_o += 2;'
441            else:
442                line = operand_name + '->id1 = IdRef(_instruction[_o++]);'
443                line += operand_name + '->id2 = IdRef(_instruction[_o++]);'
444        elif kind == 'LiteralString':
445            # The length of string in words is ceil((strlen(str) + 1) / 4).  This is equal to
446            # (strlen(str)+1+3) / 4, which is equal to strlen(str)/4+1.
447            assert (not is_array)
448            # See handling of LiteralString in get_operand_push_back_line.
449            line = 'ASSERT(IsLittleEndian());'
450            line += '*' + operand_name + ' = reinterpret_cast<const char *>(&_instruction[_o]);'
451            line += '_o += strlen(*' + operand_name + ') / 4 + 1;'
452        else:
453            if is_array:
454                line = operand_name + '->emplace_back(_instruction[_o++]);'
455            else:
456                line = '*' + operand_name + ' = ' + self.get_kind_cpp_type(
457                    kind) + '(_instruction[_o++]);'
458
459        return pre + line + post
460
461    def process_operand(self, operand, operand_distinguisher, cpp_operands_in, cpp_operands_out,
462                        cpp_in_parse_lines, cpp_out_push_back_lines):
463        operand_name = self.get_operand_name(operand, operand_distinguisher)
464        type_in, type_out, is_array, is_optional = self.get_operand_type_in_and_out(operand)
465
466        # Make the parameter list
467        cpp_operands_in.append(', ' + type_in + ' ' + operand_name)
468        cpp_operands_out.append(', ' + type_out + ' ' + operand_name)
469
470        # Make the builder body lines
471        cpp_out_push_back_lines.append(
472            self.get_operand_push_back_line(operand, operand_name, is_array, is_optional))
473
474        # Make the parser body lines
475        cpp_in_parse_lines.append(
476            self.get_operand_parse_line(operand, operand_name, is_array, is_optional))
477
478    def generate_instruction_functions(self, instruction):
479        name = instruction['opname']
480        assert (name.startswith('Op'))
481        name = name[2:]
482
483        # Skip if the instruction depends on a capability or extension we aren't interested in
484        if self.requires_unsupported_capability(instruction):
485            return
486        if self.requires_unsupported_extension(instruction):
487            return
488
489        operands = instruction.get('operands', [])
490
491        # Skip if any of the operands are not supported
492        if any([operand['kind'] in self.unsupported_kinds for operand in operands]):
493            return
494
495        cpp_operands_in = []
496        cpp_operands_out = []
497        cpp_in_parse_lines = []
498        cpp_out_push_back_lines = []
499
500        operand_distinguisher = 0
501        for operand in operands:
502            operand_distinguisher += 1
503
504            self.process_operand(operand, operand_distinguisher, cpp_operands_in, cpp_operands_out,
505                                 cpp_in_parse_lines, cpp_out_push_back_lines)
506
507            # get_operand_parse_line relies on there only being one array parameter, and it being
508            # the last.
509            assert (operand.get('quantifier') != '*' or len(cpp_in_parse_lines) == len(operands))
510
511            if operand['kind'] == 'Decoration':
512                # Special handling of Op*Decorate instructions with a Decoration operand.  That
513                # operand always comes last, and implies a number of LiteralIntegers to follow.
514                assert (len(cpp_in_parse_lines) == len(operands))
515
516                decoration_operands = {
517                    'name': 'values',
518                    'kind': 'LiteralInteger',
519                    'quantifier': '*'
520                }
521                self.process_operand(decoration_operands, operand_distinguisher, cpp_operands_in,
522                                     cpp_operands_out, cpp_in_parse_lines, cpp_out_push_back_lines)
523
524            elif operand['kind'] == 'ExecutionMode':
525                # Special handling of OpExecutionMode instruction with an ExecutionMode operand.
526                # That operand always comes last, and implies a number of LiteralIntegers to follow.
527                assert (len(cpp_in_parse_lines) == len(operands))
528
529                execution_mode_operands = {
530                    'name': 'operands',
531                    'kind': 'LiteralInteger',
532                    'quantifier': '*'
533                }
534                self.process_operand(execution_mode_operands, operand_distinguisher,
535                                     cpp_operands_in, cpp_operands_out, cpp_in_parse_lines,
536                                     cpp_out_push_back_lines)
537
538            elif operand['kind'] == 'ImageOperands':
539                # Special handling of OpImage* instructions with an ImageOperands operand.  That
540                # operand always comes last, and implies a number of IdRefs to follow with different
541                # meanings based on the bits set in said operand.
542                assert (len(cpp_in_parse_lines) == len(operands))
543
544                image_operands = {'name': 'imageOperandIds', 'kind': 'IdRef', 'quantifier': '*'}
545                self.process_operand(image_operands, operand_distinguisher, cpp_operands_in,
546                                     cpp_operands_out, cpp_in_parse_lines, cpp_out_push_back_lines)
547
548        # Make the builder prototype body
549        builder_prototype = TEMPLATE_BUILDER_FUNCTION_PROTOTYPE.format(
550            op=name, param_list=''.join(cpp_operands_in))
551        self.instruction_builder_prototypes.append(builder_prototype + ';\n')
552        self.instruction_builder_impl.append(
553            builder_prototype + '\n' + TEMPLATE_BUILDER_FUNCTION_BODY.format(
554                op=name, push_back_lines='\n'.join(cpp_out_push_back_lines)))
555
556        if len(operands) == 0:
557            return
558
559        parser_prototype = TEMPLATE_PARSER_FUNCTION_PROTOTYPE.format(
560            op=name, param_list=''.join(cpp_operands_out))
561        self.instruction_parser_prototypes.append(parser_prototype + ';\n')
562        self.instruction_parser_impl.append(
563            parser_prototype + '\n' + TEMPLATE_PARSER_FUNCTION_BODY.format(
564                op=name, parse_lines='\n'.join(cpp_in_parse_lines)))
565
566
567def main():
568
569    # auto_script parameters.
570    if len(sys.argv) > 1:
571        if sys.argv[1] == 'inputs':
572            print(SPIRV_GRAMMAR_FILE)
573        elif sys.argv[1] == 'outputs':
574            output_files_base = [SPIRV_BUILDER_FILE, SPIRV_PARSER_FILE]
575            output_files = [
576                '_autogen.'.join(pair)
577                for pair in itertools.product(output_files_base, ['h', 'cpp'])
578            ]
579            print(','.join(output_files))
580        else:
581            print('Invalid script parameters')
582            return 1
583        return 0
584
585    writer = Writer()
586    writer.write_builder_and_parser()
587
588    return 0
589
590
591if __name__ == '__main__':
592    sys.exit(main())
593