1.1 --- a/translator.py Mon Feb 27 23:43:24 2017 +0100
1.2 +++ b/translator.py Tue Feb 28 00:00:09 2017 +0100
1.3 @@ -19,22 +19,26 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 -from common import CommonModule, CommonOutput, InstructionSequence, \
1.8 +from common import CommonModule, CommonOutput, \
1.9 first, get_builtin_class, init_item, is_newer, \
1.10 predefined_constants
1.11 from encoders import encode_access_instruction, encode_access_instruction_arg, \
1.12 - encode_function_pointer, encode_literal_constant, \
1.13 - encode_literal_instantiator, encode_instantiator_pointer, \
1.14 - encode_instructions, encode_path, \
1.15 - encode_symbol, encode_type_attribute, \
1.16 - is_type_attribute
1.17 + encode_function_pointer, encode_literal_instantiator, \
1.18 + encode_instantiator_pointer, encode_path, encode_symbol, \
1.19 + encode_type_attribute, is_type_attribute
1.20 from errors import InspectError, TranslateError
1.21 from os.path import exists, join
1.22 from os import makedirs
1.23 from referencing import Reference
1.24 +from results import Result
1.25 +from transresults import TrConstantValueRef, TrInstanceRef, \
1.26 + TrLiteralSequenceRef, TrResolvedNameRef, \
1.27 + AttrResult, Expression, InstantiationResult, \
1.28 + InvocationResult, LogicalOperationResult, \
1.29 + LogicalResult, NegationResult, PredefinedConstantRef, \
1.30 + ReturnRef
1.31 from StringIO import StringIO
1.32 import compiler
1.33 -import results
1.34 import sys
1.35
1.36 class Translator(CommonOutput):
1.37 @@ -76,310 +80,13 @@
1.38 tm = TranslatedModule(module.name, self.importer, self.deducer, self.optimiser)
1.39 tm.translate(module.filename, output_filename)
1.40
1.41 -# Classes representing intermediate translation results.
1.42 -
1.43 -class ReturnRef:
1.44 -
1.45 - "Indicates usage of a return statement."
1.46 -
1.47 - pass
1.48 -
1.49 -class Expression(results.Result):
1.50 -
1.51 - "A general expression."
1.52 -
1.53 - def __init__(self, s):
1.54 - self.s = s
1.55 - def __str__(self):
1.56 - return self.s
1.57 - def __repr__(self):
1.58 - return "Expression(%r)" % self.s
1.59 -
1.60 -class TrResolvedNameRef(results.ResolvedNameRef):
1.61 -
1.62 - "A reference to a name in the translation."
1.63 -
1.64 - def __init__(self, name, ref, expr=None, is_global=False, parameter=None, location=None):
1.65 - results.ResolvedNameRef.__init__(self, name, ref, expr, is_global)
1.66 - self.parameter = parameter
1.67 - self.location = location
1.68 -
1.69 - def access_location(self):
1.70 - return self.location
1.71 -
1.72 - def __str__(self):
1.73 -
1.74 - "Return an output representation of the referenced name."
1.75 -
1.76 - # For sources, any identified static origin will be constant and thus
1.77 - # usable directly. For targets, no constant should be assigned and thus
1.78 - # the alias (or any plain name) will be used.
1.79 -
1.80 - ref = self.static()
1.81 - origin = ref and self.get_origin()
1.82 - static_name = origin and encode_path(origin)
1.83 -
1.84 - # Determine whether a qualified name is involved.
1.85 -
1.86 - t = (not self.is_constant_alias() and self.get_name() or self.name).rsplit(".", 1)
1.87 - parent = len(t) > 1 and t[0] or None
1.88 - attrname = t[-1] and encode_path(t[-1])
1.89 -
1.90 - # Assignments.
1.91 -
1.92 - if self.expr:
1.93 -
1.94 - # Eliminate assignments between constants.
1.95 -
1.96 - if ref and isinstance(self.expr, results.ResolvedNameRef) and self.expr.static():
1.97 - return ""
1.98 -
1.99 - # Qualified names must be converted into parent-relative assignments.
1.100 -
1.101 - elif parent:
1.102 - return "__store_via_object(&%s, %s, %s)" % (
1.103 - encode_path(parent), attrname, self.expr)
1.104 -
1.105 - # All other assignments involve the names as they were given.
1.106 -
1.107 - else:
1.108 - return "(%s%s) = %s" % (self.parameter and "*" or "", attrname, self.expr)
1.109 -
1.110 - # Expressions.
1.111 -
1.112 - elif static_name:
1.113 - parent = ref.parent()
1.114 - context = ref.has_kind("<function>") and encode_path(parent) or None
1.115 - return "__ATTRVALUE(&%s)" % static_name
1.116 -
1.117 - # Qualified names must be converted into parent-relative accesses.
1.118 -
1.119 - elif parent:
1.120 - return "__load_via_object(&%s, %s)" % (
1.121 - encode_path(parent), attrname)
1.122 -
1.123 - # All other accesses involve the names as they were given.
1.124 -
1.125 - else:
1.126 - return "(%s%s)" % (self.parameter and "*" or "", attrname)
1.127 -
1.128 -class TrConstantValueRef(results.ConstantValueRef):
1.129 -
1.130 - "A constant value reference in the translation."
1.131 -
1.132 - def __str__(self):
1.133 - return encode_literal_constant(self.number)
1.134 -
1.135 -class TrLiteralSequenceRef(results.LiteralSequenceRef):
1.136 -
1.137 - "A reference representing a sequence of values."
1.138 -
1.139 - def __str__(self):
1.140 - return str(self.node)
1.141 -
1.142 -class TrInstanceRef(results.InstanceRef):
1.143 -
1.144 - "A reference representing instantiation of a class."
1.145 -
1.146 - def __init__(self, ref, expr):
1.147 -
1.148 - """
1.149 - Initialise the reference with 'ref' indicating the nature of the
1.150 - reference and 'expr' being an expression used to create the instance.
1.151 - """
1.152 -
1.153 - results.InstanceRef.__init__(self, ref)
1.154 - self.expr = expr
1.155 -
1.156 - def __str__(self):
1.157 - return self.expr
1.158 -
1.159 - def __repr__(self):
1.160 - return "TrResolvedInstanceRef(%r, %r)" % (self.ref, self.expr)
1.161 -
1.162 -class AttrResult(results.Result, InstructionSequence):
1.163 -
1.164 - "A translation result for an attribute access."
1.165 -
1.166 - def __init__(self, instructions, refs, location, context_identity):
1.167 - InstructionSequence.__init__(self, instructions)
1.168 - self.refs = refs
1.169 - self.location = location
1.170 - self.context_identity = context_identity
1.171 -
1.172 - def references(self):
1.173 - return self.refs
1.174 -
1.175 - def access_location(self):
1.176 - return self.location
1.177 -
1.178 - def context(self):
1.179 - return self.context_identity
1.180 -
1.181 - def get_origin(self):
1.182 - return self.refs and len(self.refs) == 1 and first(self.refs).get_origin()
1.183 -
1.184 - def has_kind(self, kinds):
1.185 - if not self.refs:
1.186 - return False
1.187 - for ref in self.refs:
1.188 - if ref.has_kind(kinds):
1.189 - return True
1.190 - return False
1.191 -
1.192 - def __nonzero__(self):
1.193 - return bool(self.instructions)
1.194 -
1.195 - def __str__(self):
1.196 - return encode_instructions(self.instructions)
1.197 -
1.198 - def __repr__(self):
1.199 - return "AttrResult(%r, %r, %r)" % (self.instructions, self.refs, self.location)
1.200 -
1.201 -class InvocationResult(results.Result, InstructionSequence):
1.202 -
1.203 - "A translation result for an invocation."
1.204 -
1.205 - def __str__(self):
1.206 - return encode_instructions(self.instructions)
1.207 -
1.208 - def __repr__(self):
1.209 - return "InvocationResult(%r)" % self.instructions
1.210 -
1.211 -class InstantiationResult(InvocationResult, TrInstanceRef):
1.212 -
1.213 - "An instantiation result acting like an invocation result."
1.214 -
1.215 - def __init__(self, ref, instructions):
1.216 - results.InstanceRef.__init__(self, ref)
1.217 - InvocationResult.__init__(self, instructions)
1.218 -
1.219 - def __repr__(self):
1.220 - return "InstantiationResult(%r, %r)" % (self.ref, self.instructions)
1.221 -
1.222 -class PredefinedConstantRef(results.Result):
1.223 -
1.224 - "A predefined constant reference."
1.225 -
1.226 - def __init__(self, value, expr=None):
1.227 - self.value = value
1.228 - self.expr = expr
1.229 -
1.230 - def __str__(self):
1.231 -
1.232 - # Eliminate predefined constant assignments.
1.233 -
1.234 - if self.expr:
1.235 - return ""
1.236 -
1.237 - # Generate the specific constants.
1.238 -
1.239 - if self.value in ("False", "True"):
1.240 - return encode_path("__builtins__.boolean.%s" % self.value)
1.241 - elif self.value == "None":
1.242 - return encode_path("__builtins__.none.%s" % self.value)
1.243 - elif self.value == "NotImplemented":
1.244 - return encode_path("__builtins__.notimplemented.%s" % self.value)
1.245 - else:
1.246 - return self.value
1.247 -
1.248 - def __repr__(self):
1.249 - return "PredefinedConstantRef(%r)" % self.value
1.250 -
1.251 -class LogicalResult(results.Result):
1.252 -
1.253 - "A logical expression result."
1.254 -
1.255 - def _convert(self, expr):
1.256 -
1.257 - "Return 'expr' converted to a testable value."
1.258 -
1.259 - if isinstance(expr, LogicalResult):
1.260 - return expr.apply_test()
1.261 - else:
1.262 - return "__BOOL(%s)" % expr
1.263 -
1.264 -class NegationResult(LogicalResult):
1.265 -
1.266 - "A negation expression result."
1.267 -
1.268 - def __init__(self, expr):
1.269 - self.expr = expr
1.270 -
1.271 - def apply_test(self):
1.272 -
1.273 - "Return the result in a form suitable for direct testing."
1.274 -
1.275 - expr = self._convert(self.expr)
1.276 - return "(!%s)" % expr
1.277 -
1.278 - def __str__(self):
1.279 - return "(%s ? %s : %s)" % (
1.280 - self._convert(self.expr),
1.281 - PredefinedConstantRef("False"),
1.282 - PredefinedConstantRef("True"))
1.283 -
1.284 - def __repr__(self):
1.285 - return "NegationResult(%r)" % self.expr
1.286 -
1.287 -class LogicalOperationResult(LogicalResult):
1.288 -
1.289 - "A logical operation result."
1.290 -
1.291 - def __init__(self, exprs, conjunction):
1.292 - self.exprs = exprs
1.293 - self.conjunction = conjunction
1.294 -
1.295 - def apply_test(self):
1.296 -
1.297 - """
1.298 - Return the result in a form suitable for direct testing.
1.299 -
1.300 - Convert ... to ...
1.301 -
1.302 - <a> and <b>
1.303 - ((__BOOL(<a>)) && (__BOOL(<b>)))
1.304 -
1.305 - <a> or <b>
1.306 - ((__BOOL(<a>)) || (__BOOL(<b>)))
1.307 - """
1.308 -
1.309 - results = []
1.310 - for expr in self.exprs:
1.311 - results.append(self._convert(expr))
1.312 -
1.313 - if self.conjunction:
1.314 - return "(%s)" % " && ".join(results)
1.315 - else:
1.316 - return "(%s)" % " || ".join(results)
1.317 -
1.318 - def __str__(self):
1.319 -
1.320 - """
1.321 - Convert ... to ...
1.322 -
1.323 - <a> and <b>
1.324 - (__tmp_result = <a>, !__BOOL(__tmp_result)) ? __tmp_result : <b>
1.325 -
1.326 - <a> or <b>
1.327 - (__tmp_result = <a>, __BOOL(__tmp_result)) ? __tmp_result : <b>
1.328 - """
1.329 -
1.330 - results = []
1.331 - for expr in self.exprs[:-1]:
1.332 - results.append("(__tmp_result = %s, %s__BOOL(__tmp_result)) ? __tmp_result : " % (expr, self.conjunction and "!" or ""))
1.333 - results.append(str(self.exprs[-1]))
1.334 -
1.335 - return "(%s)" % "".join(results)
1.336 -
1.337 - def __repr__(self):
1.338 - return "LogicalOperationResult(%r, %r)" % (self.exprs, self.conjunction)
1.339 +
1.340
1.341 def make_expression(expr):
1.342
1.343 "Make a new expression from the existing 'expr'."
1.344
1.345 - if isinstance(expr, results.Result):
1.346 + if isinstance(expr, Result):
1.347 return expr
1.348 else:
1.349 return Expression(str(expr))
1.350 @@ -616,7 +323,7 @@
1.351
1.352 # Handle processing requests on results.
1.353
1.354 - if isinstance(node, results.Result):
1.355 + if isinstance(node, Result):
1.356 return node
1.357
1.358 # Handle processing requests on nodes.
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/transresults.py Tue Feb 28 00:00:09 2017 +0100
2.3 @@ -0,0 +1,326 @@
2.4 +#!/usr/bin/env python
2.5 +
2.6 +"""
2.7 +Translation result abstractions.
2.8 +
2.9 +Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>
2.10 +
2.11 +This program is free software; you can redistribute it and/or modify it under
2.12 +the terms of the GNU General Public License as published by the Free Software
2.13 +Foundation; either version 3 of the License, or (at your option) any later
2.14 +version.
2.15 +
2.16 +This program is distributed in the hope that it will be useful, but WITHOUT
2.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
2.19 +details.
2.20 +
2.21 +You should have received a copy of the GNU General Public License along with
2.22 +this program. If not, see <http://www.gnu.org/licenses/>.
2.23 +"""
2.24 +
2.25 +from common import first, InstructionSequence
2.26 +from encoders import encode_instructions, encode_literal_constant, encode_path
2.27 +from results import ConstantValueRef, InstanceRef, LiteralSequenceRef, \
2.28 + ResolvedNameRef, Result
2.29 +
2.30 +# Classes representing intermediate translation results.
2.31 +
2.32 +class ReturnRef:
2.33 +
2.34 + "Indicates usage of a return statement."
2.35 +
2.36 + pass
2.37 +
2.38 +class Expression(Result):
2.39 +
2.40 + "A general expression."
2.41 +
2.42 + def __init__(self, s):
2.43 + self.s = s
2.44 + def __str__(self):
2.45 + return self.s
2.46 + def __repr__(self):
2.47 + return "Expression(%r)" % self.s
2.48 +
2.49 +class TrResolvedNameRef(ResolvedNameRef):
2.50 +
2.51 + "A reference to a name in the translation."
2.52 +
2.53 + def __init__(self, name, ref, expr=None, is_global=False, parameter=None, location=None):
2.54 + ResolvedNameRef.__init__(self, name, ref, expr, is_global)
2.55 + self.parameter = parameter
2.56 + self.location = location
2.57 +
2.58 + def access_location(self):
2.59 + return self.location
2.60 +
2.61 + def __str__(self):
2.62 +
2.63 + "Return an output representation of the referenced name."
2.64 +
2.65 + # For sources, any identified static origin will be constant and thus
2.66 + # usable directly. For targets, no constant should be assigned and thus
2.67 + # the alias (or any plain name) will be used.
2.68 +
2.69 + ref = self.static()
2.70 + origin = ref and self.get_origin()
2.71 + static_name = origin and encode_path(origin)
2.72 +
2.73 + # Determine whether a qualified name is involved.
2.74 +
2.75 + t = (not self.is_constant_alias() and self.get_name() or self.name).rsplit(".", 1)
2.76 + parent = len(t) > 1 and t[0] or None
2.77 + attrname = t[-1] and encode_path(t[-1])
2.78 +
2.79 + # Assignments.
2.80 +
2.81 + if self.expr:
2.82 +
2.83 + # Eliminate assignments between constants.
2.84 +
2.85 + if ref and isinstance(self.expr, ResolvedNameRef) and self.expr.static():
2.86 + return ""
2.87 +
2.88 + # Qualified names must be converted into parent-relative assignments.
2.89 +
2.90 + elif parent:
2.91 + return "__store_via_object(&%s, %s, %s)" % (
2.92 + encode_path(parent), attrname, self.expr)
2.93 +
2.94 + # All other assignments involve the names as they were given.
2.95 +
2.96 + else:
2.97 + return "(%s%s) = %s" % (self.parameter and "*" or "", attrname, self.expr)
2.98 +
2.99 + # Expressions.
2.100 +
2.101 + elif static_name:
2.102 + parent = ref.parent()
2.103 + context = ref.has_kind("<function>") and encode_path(parent) or None
2.104 + return "__ATTRVALUE(&%s)" % static_name
2.105 +
2.106 + # Qualified names must be converted into parent-relative accesses.
2.107 +
2.108 + elif parent:
2.109 + return "__load_via_object(&%s, %s)" % (
2.110 + encode_path(parent), attrname)
2.111 +
2.112 + # All other accesses involve the names as they were given.
2.113 +
2.114 + else:
2.115 + return "(%s%s)" % (self.parameter and "*" or "", attrname)
2.116 +
2.117 +class TrConstantValueRef(ConstantValueRef):
2.118 +
2.119 + "A constant value reference in the translation."
2.120 +
2.121 + def __str__(self):
2.122 + return encode_literal_constant(self.number)
2.123 +
2.124 +class TrLiteralSequenceRef(LiteralSequenceRef):
2.125 +
2.126 + "A reference representing a sequence of values."
2.127 +
2.128 + def __str__(self):
2.129 + return str(self.node)
2.130 +
2.131 +class TrInstanceRef(InstanceRef):
2.132 +
2.133 + "A reference representing instantiation of a class."
2.134 +
2.135 + def __init__(self, ref, expr):
2.136 +
2.137 + """
2.138 + Initialise the reference with 'ref' indicating the nature of the
2.139 + reference and 'expr' being an expression used to create the instance.
2.140 + """
2.141 +
2.142 + InstanceRef.__init__(self, ref)
2.143 + self.expr = expr
2.144 +
2.145 + def __str__(self):
2.146 + return self.expr
2.147 +
2.148 + def __repr__(self):
2.149 + return "TrResolvedInstanceRef(%r, %r)" % (self.ref, self.expr)
2.150 +
2.151 +class AttrResult(Result, InstructionSequence):
2.152 +
2.153 + "A translation result for an attribute access."
2.154 +
2.155 + def __init__(self, instructions, refs, location, context_identity):
2.156 + InstructionSequence.__init__(self, instructions)
2.157 + self.refs = refs
2.158 + self.location = location
2.159 + self.context_identity = context_identity
2.160 +
2.161 + def references(self):
2.162 + return self.refs
2.163 +
2.164 + def access_location(self):
2.165 + return self.location
2.166 +
2.167 + def context(self):
2.168 + return self.context_identity
2.169 +
2.170 + def get_origin(self):
2.171 + return self.refs and len(self.refs) == 1 and first(self.refs).get_origin()
2.172 +
2.173 + def has_kind(self, kinds):
2.174 + if not self.refs:
2.175 + return False
2.176 + for ref in self.refs:
2.177 + if ref.has_kind(kinds):
2.178 + return True
2.179 + return False
2.180 +
2.181 + def __nonzero__(self):
2.182 + return bool(self.instructions)
2.183 +
2.184 + def __str__(self):
2.185 + return encode_instructions(self.instructions)
2.186 +
2.187 + def __repr__(self):
2.188 + return "AttrResult(%r, %r, %r)" % (self.instructions, self.refs, self.location)
2.189 +
2.190 +class InvocationResult(Result, InstructionSequence):
2.191 +
2.192 + "A translation result for an invocation."
2.193 +
2.194 + def __str__(self):
2.195 + return encode_instructions(self.instructions)
2.196 +
2.197 + def __repr__(self):
2.198 + return "InvocationResult(%r)" % self.instructions
2.199 +
2.200 +class InstantiationResult(InvocationResult, TrInstanceRef):
2.201 +
2.202 + "An instantiation result acting like an invocation result."
2.203 +
2.204 + def __init__(self, ref, instructions):
2.205 + InstanceRef.__init__(self, ref)
2.206 + InvocationResult.__init__(self, instructions)
2.207 +
2.208 + def __repr__(self):
2.209 + return "InstantiationResult(%r, %r)" % (self.ref, self.instructions)
2.210 +
2.211 +class PredefinedConstantRef(Result):
2.212 +
2.213 + "A predefined constant reference."
2.214 +
2.215 + def __init__(self, value, expr=None):
2.216 + self.value = value
2.217 + self.expr = expr
2.218 +
2.219 + def __str__(self):
2.220 +
2.221 + # Eliminate predefined constant assignments.
2.222 +
2.223 + if self.expr:
2.224 + return ""
2.225 +
2.226 + # Generate the specific constants.
2.227 +
2.228 + if self.value in ("False", "True"):
2.229 + return encode_path("__builtins__.boolean.%s" % self.value)
2.230 + elif self.value == "None":
2.231 + return encode_path("__builtins__.none.%s" % self.value)
2.232 + elif self.value == "NotImplemented":
2.233 + return encode_path("__builtins__.notimplemented.%s" % self.value)
2.234 + else:
2.235 + return self.value
2.236 +
2.237 + def __repr__(self):
2.238 + return "PredefinedConstantRef(%r)" % self.value
2.239 +
2.240 +class LogicalResult(Result):
2.241 +
2.242 + "A logical expression result."
2.243 +
2.244 + def _convert(self, expr):
2.245 +
2.246 + "Return 'expr' converted to a testable value."
2.247 +
2.248 + if isinstance(expr, LogicalResult):
2.249 + return expr.apply_test()
2.250 + else:
2.251 + return "__BOOL(%s)" % expr
2.252 +
2.253 +class NegationResult(LogicalResult):
2.254 +
2.255 + "A negation expression result."
2.256 +
2.257 + def __init__(self, expr):
2.258 + self.expr = expr
2.259 +
2.260 + def apply_test(self):
2.261 +
2.262 + "Return the result in a form suitable for direct testing."
2.263 +
2.264 + expr = self._convert(self.expr)
2.265 + return "(!%s)" % expr
2.266 +
2.267 + def __str__(self):
2.268 + return "(%s ? %s : %s)" % (
2.269 + self._convert(self.expr),
2.270 + PredefinedConstantRef("False"),
2.271 + PredefinedConstantRef("True"))
2.272 +
2.273 + def __repr__(self):
2.274 + return "NegationResult(%r)" % self.expr
2.275 +
2.276 +class LogicalOperationResult(LogicalResult):
2.277 +
2.278 + "A logical operation result."
2.279 +
2.280 + def __init__(self, exprs, conjunction):
2.281 + self.exprs = exprs
2.282 + self.conjunction = conjunction
2.283 +
2.284 + def apply_test(self):
2.285 +
2.286 + """
2.287 + Return the result in a form suitable for direct testing.
2.288 +
2.289 + Convert ... to ...
2.290 +
2.291 + <a> and <b>
2.292 + ((__BOOL(<a>)) && (__BOOL(<b>)))
2.293 +
2.294 + <a> or <b>
2.295 + ((__BOOL(<a>)) || (__BOOL(<b>)))
2.296 + """
2.297 +
2.298 + results = []
2.299 + for expr in self.exprs:
2.300 + results.append(self._convert(expr))
2.301 +
2.302 + if self.conjunction:
2.303 + return "(%s)" % " && ".join(results)
2.304 + else:
2.305 + return "(%s)" % " || ".join(results)
2.306 +
2.307 + def __str__(self):
2.308 +
2.309 + """
2.310 + Convert ... to ...
2.311 +
2.312 + <a> and <b>
2.313 + (__tmp_result = <a>, !__BOOL(__tmp_result)) ? __tmp_result : <b>
2.314 +
2.315 + <a> or <b>
2.316 + (__tmp_result = <a>, __BOOL(__tmp_result)) ? __tmp_result : <b>
2.317 + """
2.318 +
2.319 + results = []
2.320 + for expr in self.exprs[:-1]:
2.321 + results.append("(__tmp_result = %s, %s__BOOL(__tmp_result)) ? __tmp_result : " % (expr, self.conjunction and "!" or ""))
2.322 + results.append(str(self.exprs[-1]))
2.323 +
2.324 + return "(%s)" % "".join(results)
2.325 +
2.326 + def __repr__(self):
2.327 + return "LogicalOperationResult(%r, %r)" % (self.exprs, self.conjunction)
2.328 +
2.329 +# vim: tabstop=4 expandtab shiftwidth=4