File: unstone.stoneg.py

package info (click to toggle)
python-stone 3.3.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,036 kB
  • sloc: python: 22,311; objc: 498; sh: 23; makefile: 11
file content (143 lines) | stat: -rw-r--r-- 5,819 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""Example backend that outputs a Stone file equivalent to the input file.

Current limitations:
- Whitespace is not reproduced exactly (this may be a feature)
- Order of definitions is lost
- Comments are lost
- Aliases are lost (they are expanded in-line)
- Docstrings are reformatted
"""

from stone.backend import CodeBackend
from stone.frontend.ast import AstTypeRef
from stone.ir import DataType
from stone.ir import List, String, Timestamp
from stone.ir import Struct, Union, Void
from stone.ir.data_types import _BoundedInteger, _BoundedFloat


class UnstoneBackend(CodeBackend):
    """Main class.

    The Stone CLI finds this class through introspection."""

    def generate(self, api):
        """Main code generator entry point."""
        # Create a file for each namespace.
        for namespace in api.namespaces.values():
            with self.output_to_relative_path('%s.stone' % namespace.name):
                # Output a namespace header.
                self.emit('namespace %s' % namespace.name)
                # Output all data type (struct and union) definitions.
                for data_type in namespace.linearize_data_types():
                    self.generate_data_type(data_type)
                # Output all route definitions.
                for route in namespace.routes:
                    self.generate_route(route)

    def generate_data_type(self, data_type):
        """Output a data type definition (a struct or union)."""
        if isinstance(data_type, Struct):
            # Output a struct definition.
            self.emit('')
            self.emit('struct %s' % data_type.name)
            with self.indent():
                if data_type.doc is not None:
                    self.emit(self.format_string(data_type.doc))
                for field in data_type.fields:
                    type_repr = self.format_data_type(field.data_type)
                    if not field.has_default:
                        self.emit('{} {}'.format(field.name, type_repr))
                    else:
                        self.emit('%s %s = %s' %
                            (field.name, type_repr, self.format_value(field.default)))
                    if field.doc is not None:
                        with self.indent():
                            self.emit(self.format_value(field.doc))
        elif isinstance(data_type, Union):
            # Output a union definition.
            self.emit('')
            self.emit('union %s' % data_type.name)
            with self.indent():
                if data_type.doc is not None:
                    self.emit(self.format_string(data_type.doc))
                for field in data_type.fields:
                    name = field.name
                    # Add a star for a catch-all field.
                    # (There are two ways to recognize these.)
                    if field.catch_all or field is data_type.catch_all_field:
                        name += '*'
                    if isinstance(field.data_type, Void):
                        self.emit('%s' % (name))
                    else:
                        type_repr = self.format_data_type(field.data_type)
                        self.emit('{} {}'.format(name, type_repr))
                    if field.doc is not None:
                        with self.indent():
                            self.emit(self.format_value(field.doc))
        else:
            # Don't know what this is.
            self.emit('')
            self.emit('# ??? %s' % repr(data_type))

    def generate_route(self, route):
        """Output a route definition."""
        self.emit('')
        self.emit('route {} ({}, {}, {})'.format(
            route.name,
            self.format_data_type(route.arg_data_type),
            self.format_data_type(route.result_data_type),
            self.format_data_type(route.error_data_type)
        ))
        # Output the docstring.
        with self.indent():
            if route.doc is not None:
                self.emit(self.format_string(route.doc))

    # Table describing data types with parameters.
    _data_type_map = [
        (_BoundedInteger, ['min_value', 'max_value']),
        (_BoundedFloat, ['min_value', 'max_value']),
        (List, ['data_type', 'min_items', 'max_items']),
        (String, ['min_length', 'max_length', 'pattern']),
        (Timestamp, ['format']),
    ]

    def format_data_type(self, data_type):
        """Helper function to format a data type.

        This returns the name if it's a struct or union, otherwise
        (i.e. for primitive types) it renders the name and the
        parameters.
        """
        s = data_type.name
        for type_class, key_list in self._data_type_map:
            if isinstance(data_type, type_class):
                args = []
                for key in key_list:
                    val = getattr(data_type, key)
                    if val is not None:
                        if isinstance(val, AstTypeRef):
                            sval = val.name
                        elif isinstance(val, DataType):
                            sval = self.format_data_type(val)
                        else:
                            sval = self.format_value(val)
                        args.append(key + '=' + sval)
                if args:
                    s += '(' + ', '.join(args) + ')'
                break
        if data_type.nullable:
            s += '?'
        return s

    def format_value(self, val):
        """Helper function to format a value."""
        if isinstance(val, str):
            return self.format_string(val)
        else:
            return str(val)

    def format_string(self, val):
        """Helper function to format a string."""
        return '"' + val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\n\n') + '"'