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') + '"'
|