micropython

Changeset

438:b547786f38c7
2011-07-02 Paul Boddie raw files shortlog changelog graph Moved some code generation methods into a new Assembler class. Separated sequence element storage into a separate method which may form the basis of a native library routine.
micropython/ast.py (file) micropython/code.py (file) micropython/trans.py (file)
     1.1 --- a/micropython/ast.py	Sun Jun 26 23:54:37 2011 +0200
     1.2 +++ b/micropython/ast.py	Sat Jul 02 02:41:33 2011 +0200
     1.3 @@ -19,18 +19,18 @@
     1.4  this program.  If not, see <http://www.gnu.org/licenses/>.
     1.5  """
     1.6  
     1.7 -from micropython.opt import Optimiser
     1.8  from micropython.common import *
     1.9  from micropython.data import *
    1.10  from micropython.rsvp import *
    1.11  from micropython.trans import Helper
    1.12 +from micropython.code import Assembler
    1.13  import compiler.ast
    1.14  
    1.15  # Program visitors.
    1.16  
    1.17 -class Translation(ASTVisitor, Helper):
    1.18 +class Translation(ASTVisitor, Assembler, Helper):
    1.19  
    1.20 -    "A translated module."
    1.21 +    "A module translator."
    1.22  
    1.23      # Attribute access instructions, for use with the appropriate handlers.
    1.24  
    1.25 @@ -53,6 +53,7 @@
    1.26          """
    1.27  
    1.28          ASTVisitor.__init__(self)
    1.29 +        Assembler.__init__(self, program.optimisations)
    1.30          self.visitor = self
    1.31          self.module = module
    1.32  
    1.33 @@ -64,54 +65,18 @@
    1.34          self.importer = self.program.get_importer()
    1.35          self.builtins = self.importer.modules.get("__builtins__")
    1.36  
    1.37 -        # Optimisation.
    1.38 -
    1.39 -        self.optimiser = Optimiser(self, program.optimisations)
    1.40 -
    1.41 -        # The current unit being translated.
    1.42 -
    1.43 -        self.unit = None
    1.44 +        # Status flags.
    1.45  
    1.46 -        # The temporary storage used by the current assignment expression.
    1.47 -
    1.48 -        self.expr_temp = []
    1.49 -
    1.50 -        # Wiring within the code.
    1.51 -
    1.52 -        self.labels = {}
    1.53 -        self.label_number = 0
    1.54 -        self.loop_blocks = []
    1.55 -        self.exception_blocks = []
    1.56          self.in_exception_handler = 0
    1.57          self.in_assignment = 0          # for slicing and subscript
    1.58  
    1.59 +        # Reset the assembler.
    1.60 +
    1.61          self.reset()
    1.62  
    1.63      def __repr__(self):
    1.64          return "Translation(%r)" % self.module
    1.65  
    1.66 -    def reset(self):
    1.67 -
    1.68 -        "Reset the state of the translator."
    1.69 -
    1.70 -        # The code itself. This is limited to the code for a particular block
    1.71 -        # being processed.
    1.72 -
    1.73 -        self.blocks = []
    1.74 -
    1.75 -        # Information about temporary values.
    1.76 -
    1.77 -        self.temp_positions = set()
    1.78 -        self.max_temp_position = -1
    1.79 -
    1.80 -        # Information about instructions which construct frames.
    1.81 -
    1.82 -        self.frame_makers = []
    1.83 -
    1.84 -        # Optimiser state must be reset for each unit.
    1.85 -
    1.86 -        self.optimiser.reset()
    1.87 -
    1.88      def get_module_code(self):
    1.89  
    1.90          """
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/micropython/code.py	Sat Jul 02 02:41:33 2011 +0200
     2.3 @@ -0,0 +1,298 @@
     2.4 +#!/usr/bin/env python
     2.5 +
     2.6 +"""
     2.7 +Generate low-level code.
     2.8 +
     2.9 +Copyright (C) 2007, 2008, 2009, 2010, 2011 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 micropython.opt import Optimiser
    2.26 +from micropython.common import *
    2.27 +from micropython.data import *
    2.28 +from micropython.rsvp import *
    2.29 +import compiler.ast
    2.30 +
    2.31 +class Assembler:
    2.32 +
    2.33 +    "Support for generating low-level code."
    2.34 +
    2.35 +    def __init__(self, optimisations):
    2.36 +
    2.37 +        "Initialise the assembler with an optimiser and status attributes."
    2.38 +
    2.39 +        # Optimisation.
    2.40 +
    2.41 +        self.optimiser = Optimiser(self, optimisations)
    2.42 +
    2.43 +        # The current unit being translated.
    2.44 +
    2.45 +        self.unit = None
    2.46 +
    2.47 +        # The temporary storage used by the current assignment expression.
    2.48 +
    2.49 +        self.expr_temp = []
    2.50 +
    2.51 +        # Wiring within the code.
    2.52 +
    2.53 +        self.labels = {}
    2.54 +        self.label_number = 0
    2.55 +        self.loop_blocks = []
    2.56 +        self.exception_blocks = []
    2.57 +
    2.58 +    def reset(self):
    2.59 +
    2.60 +        "Reset the state of the assembler."
    2.61 +
    2.62 +        # The code itself. This is limited to the code for a particular block
    2.63 +        # being processed.
    2.64 +
    2.65 +        self.blocks = []
    2.66 +
    2.67 +        # Information about temporary values.
    2.68 +
    2.69 +        self.temp_positions = set()
    2.70 +        self.max_temp_position = -1
    2.71 +
    2.72 +        # Information about instructions which construct frames.
    2.73 +
    2.74 +        self.frame_makers = []
    2.75 +
    2.76 +        # Optimiser state must be reset for each unit.
    2.77 +
    2.78 +        self.optimiser.reset()
    2.79 +
    2.80 +    def new_block(self):
    2.81 +
    2.82 +        "Return a new code block."
    2.83 +
    2.84 +        return Block()
    2.85 +
    2.86 +    def set_block(self, block):
    2.87 +
    2.88 +        "Add the given 'block' to the unit's list of blocks."
    2.89 +
    2.90 +        self.optimiser.reset()
    2.91 +        self.blocks.append(block)
    2.92 +
    2.93 +    def get_loop_blocks(self):
    2.94 +        return self.loop_blocks[-1]
    2.95 +
    2.96 +    def add_loop_blocks(self, next_block, exit_block):
    2.97 +        self.loop_blocks.append((next_block, exit_block))
    2.98 +
    2.99 +    def drop_loop_blocks(self):
   2.100 +        self.loop_blocks.pop()
   2.101 +
   2.102 +    def add_exception_unit(self):
   2.103 +        self.exception_blocks.append([])
   2.104 +
   2.105 +    def get_exception_blocks(self):
   2.106 +        return self.exception_blocks[-1][-1]
   2.107 +
   2.108 +    def add_exception_blocks(self, handler_block, exit_block):
   2.109 +        self.exception_blocks[-1].append((handler_block, exit_block))
   2.110 +
   2.111 +    def drop_exception_blocks(self):
   2.112 +        self.exception_blocks[-1].pop()
   2.113 +
   2.114 +    def drop_exception_unit(self):
   2.115 +        self.exception_blocks.pop()
   2.116 +
   2.117 +    # Assignment expression values.
   2.118 +
   2.119 +    def record_value(self, immediate=1):
   2.120 +
   2.121 +        """
   2.122 +        Record the current active value for an assignment. If the optional
   2.123 +        'immediate' parameter if set to a false value always allocates new
   2.124 +        temporary storage to hold the recorded value; otherwise, the
   2.125 +        value-providing instruction may be replicated in order to provide the
   2.126 +        active value later on.
   2.127 +        """
   2.128 +
   2.129 +        if immediate:
   2.130 +            temp = self.optimiser.optimise_temp_storage()
   2.131 +        else:
   2.132 +            temp = self.get_temp()
   2.133 +        self.expr_temp.append(temp)
   2.134 +
   2.135 +    def discard_value(self):
   2.136 +
   2.137 +        "Discard any temporary storage in use for the current assignment value."
   2.138 +
   2.139 +        self.discard_temp(self.expr_temp.pop())
   2.140 +
   2.141 +    def set_source(self):
   2.142 +
   2.143 +        """
   2.144 +        Set the source of an assignment using the current assignment value. This
   2.145 +        sets the source input for the current instruction.
   2.146 +        """
   2.147 +
   2.148 +        self.optimiser.set_source(self.expr_temp[-1])
   2.149 +
   2.150 +        # Optimise away constant storage if appropriate.
   2.151 +
   2.152 +        if self.optimiser.optimise_constant_storage():
   2.153 +            self.remove_op()
   2.154 +
   2.155 +    def is_immediate_user(self, node):
   2.156 +
   2.157 +        """
   2.158 +        Return whether 'node' is an immediate user of an assignment expression.
   2.159 +        """
   2.160 +
   2.161 +        return isinstance(node, (compiler.ast.AssName, compiler.ast.AssAttr))
   2.162 +
   2.163 +    def has_immediate_usage(self, nodes):
   2.164 +
   2.165 +        """
   2.166 +        Return whether 'nodes' are all immediate users of an assignment expression.
   2.167 +        """
   2.168 +
   2.169 +        for n in nodes:
   2.170 +            if not self.is_immediate_user(n):
   2.171 +                return 0
   2.172 +        return 1
   2.173 +
   2.174 +    # Temporary storage administration.
   2.175 +
   2.176 +    def get_temp(self):
   2.177 +
   2.178 +        """
   2.179 +        Add a temporary storage instruction for the current value and return a
   2.180 +        sequence of access instructions.
   2.181 +        """
   2.182 +
   2.183 +        position_in_frame = self.reserve_temp()
   2.184 +        self.new_op(StoreTemp(position_in_frame))
   2.185 +        return LoadTemp(position_in_frame)
   2.186 +
   2.187 +    def reserve_temp(self, temp_position=None):
   2.188 +
   2.189 +        """
   2.190 +        Reserve a new temporary storage position, or if the optional
   2.191 +        'temp_position' is specified, ensure that this particular position is
   2.192 +        reserved.
   2.193 +        """
   2.194 +
   2.195 +        if temp_position is not None:
   2.196 +            pass
   2.197 +        elif not self.temp_positions:
   2.198 +            temp_position = 0
   2.199 +        else:
   2.200 +            temp_position = max(self.temp_positions) + 1
   2.201 +
   2.202 +        self.temp_positions.add(temp_position)
   2.203 +        self.max_temp_position = max(self.max_temp_position, temp_position)
   2.204 +        return self.unit.all_local_usage + temp_position # position in frame
   2.205 +
   2.206 +    def ensure_temp(self, instruction=None):
   2.207 +
   2.208 +        """
   2.209 +        Ensure that the 'instruction' is using a reserved temporary storage
   2.210 +        position.
   2.211 +        """
   2.212 +
   2.213 +        if isinstance(instruction, LoadTemp):
   2.214 +            temp_position = instruction.attr - self.unit.all_local_usage
   2.215 +            self.reserve_temp(temp_position)
   2.216 +
   2.217 +    def discard_temp(self, instruction=None):
   2.218 +
   2.219 +        "Discard any temporary storage position used by 'instruction'."
   2.220 +
   2.221 +        if isinstance(instruction, LoadTemp):
   2.222 +            temp_position = instruction.attr - self.unit.all_local_usage
   2.223 +            self.free_temp(temp_position)
   2.224 +
   2.225 +    def free_temp(self, temp_position):
   2.226 +
   2.227 +        "Free the temporary storage position specified by 'temp_position'."
   2.228 +
   2.229 +        if temp_position in self.temp_positions:
   2.230 +            self.temp_positions.remove(temp_position)
   2.231 +
   2.232 +    def set_frame_usage(self, node, extend):
   2.233 +
   2.234 +        """
   2.235 +        Ensure that the frame usage for the unit associated with 'node' is set
   2.236 +        on the 'extend' instruction.
   2.237 +        """
   2.238 +
   2.239 +        # Remove any ExtendFrame instructions which do nothing.
   2.240 +
   2.241 +        if self.last_op() is extend:
   2.242 +            self.remove_op()
   2.243 +            return
   2.244 +
   2.245 +        ntemp = self.max_temp_position + 1
   2.246 +        extend.attr = ntemp + node.unit.local_usage # NOTE: See get_code for similar code.
   2.247 +
   2.248 +    # Code writing methods.
   2.249 +
   2.250 +    def new_op(self, op):
   2.251 +
   2.252 +        """
   2.253 +        Add 'op' to the generated code, returning a true value if an instruction
   2.254 +        was added.
   2.255 +        """
   2.256 +
   2.257 +        # Optimise load operations employed by this instruction.
   2.258 +
   2.259 +        self.optimiser.optimise_load_operations(op)
   2.260 +        if self.optimiser.optimise_away_no_operations(op) or self.optimiser.optimise_unused_handlers(op):
   2.261 +            return 0
   2.262 +
   2.263 +        # Add the operation to the current block.
   2.264 +
   2.265 +        self.blocks[-1].code.append(op)
   2.266 +        self.optimiser.set_new(op)
   2.267 +        return 1
   2.268 +
   2.269 +    def remove_op(self):
   2.270 +
   2.271 +        "Remove the last instruction."
   2.272 +
   2.273 +        op = self.blocks[-1].code.pop()
   2.274 +        self.optimiser.clear_active()
   2.275 +
   2.276 +    def replace_op(self, op):
   2.277 +
   2.278 +        "Replace the last added instruction with 'op'."
   2.279 +
   2.280 +        self.remove_op()
   2.281 +        self.new_op(op)
   2.282 +
   2.283 +    def replace_active_value(self, op):
   2.284 +
   2.285 +        """
   2.286 +        Replace the value-providing active instruction with 'op' if appropriate.
   2.287 +        """
   2.288 +
   2.289 +        self.optimiser.remove_active_value()
   2.290 +        self.new_op(op)
   2.291 +
   2.292 +    def last_op(self):
   2.293 +
   2.294 +        "Return the last added instruction."
   2.295 +
   2.296 +        try:
   2.297 +            return self.blocks[-1].code[-1]
   2.298 +        except IndexError:
   2.299 +            return None
   2.300 +
   2.301 +# vim: tabstop=4 expandtab shiftwidth=4
     3.1 --- a/micropython/trans.py	Sun Jun 26 23:54:37 2011 +0200
     3.2 +++ b/micropython/trans.py	Sat Jul 02 02:41:33 2011 +0200
     3.3 @@ -89,229 +89,6 @@
     3.4          else:
     3.5              raise TranslateError("No __builtins__ module is available for name %r." % name)
     3.6  
     3.7 -    # Code feature methods.
     3.8 -
     3.9 -    def new_block(self):
    3.10 -
    3.11 -        "Return a new code block."
    3.12 -
    3.13 -        return Block()
    3.14 -
    3.15 -    def set_block(self, block):
    3.16 -
    3.17 -        "Add the given 'block' to the unit's list of blocks."
    3.18 -
    3.19 -        self.optimiser.reset()
    3.20 -        self.blocks.append(block)
    3.21 -
    3.22 -    def get_loop_blocks(self):
    3.23 -        return self.loop_blocks[-1]
    3.24 -
    3.25 -    def add_loop_blocks(self, next_block, exit_block):
    3.26 -        self.loop_blocks.append((next_block, exit_block))
    3.27 -
    3.28 -    def drop_loop_blocks(self):
    3.29 -        self.loop_blocks.pop()
    3.30 -
    3.31 -    def add_exception_unit(self):
    3.32 -        self.exception_blocks.append([])
    3.33 -
    3.34 -    def get_exception_blocks(self):
    3.35 -        return self.exception_blocks[-1][-1]
    3.36 -
    3.37 -    def add_exception_blocks(self, handler_block, exit_block):
    3.38 -        self.exception_blocks[-1].append((handler_block, exit_block))
    3.39 -
    3.40 -    def drop_exception_blocks(self):
    3.41 -        self.exception_blocks[-1].pop()
    3.42 -
    3.43 -    def drop_exception_unit(self):
    3.44 -        self.exception_blocks.pop()
    3.45 -
    3.46 -    # Assignment expression values.
    3.47 -
    3.48 -    def record_value(self, immediate=1):
    3.49 -
    3.50 -        """
    3.51 -        Record the current active value for an assignment. If the optional
    3.52 -        'immediate' parameter if set to a false value always allocates new
    3.53 -        temporary storage to hold the recorded value; otherwise, the
    3.54 -        value-providing instruction may be replicated in order to provide the
    3.55 -        active value later on.
    3.56 -        """
    3.57 -
    3.58 -        if immediate:
    3.59 -            temp = self.optimiser.optimise_temp_storage()
    3.60 -        else:
    3.61 -            temp = self.get_temp()
    3.62 -        self.expr_temp.append(temp)
    3.63 -
    3.64 -    def discard_value(self):
    3.65 -
    3.66 -        "Discard any temporary storage in use for the current assignment value."
    3.67 -
    3.68 -        self.discard_temp(self.expr_temp.pop())
    3.69 -
    3.70 -    def set_source(self):
    3.71 -
    3.72 -        """
    3.73 -        Set the source of an assignment using the current assignment value. This
    3.74 -        sets the source input for the current instruction.
    3.75 -        """
    3.76 -
    3.77 -        self.optimiser.set_source(self.expr_temp[-1])
    3.78 -
    3.79 -        # Optimise away constant storage if appropriate.
    3.80 -
    3.81 -        if self.optimiser.optimise_constant_storage():
    3.82 -            self.remove_op()
    3.83 -
    3.84 -    def is_immediate_user(self, node):
    3.85 -
    3.86 -        """
    3.87 -        Return whether 'node' is an immediate user of an assignment expression.
    3.88 -        """
    3.89 -
    3.90 -        return isinstance(node, (compiler.ast.AssName, compiler.ast.AssAttr))
    3.91 -
    3.92 -    def has_immediate_usage(self, nodes):
    3.93 -
    3.94 -        """
    3.95 -        Return whether 'nodes' are all immediate users of an assignment expression.
    3.96 -        """
    3.97 -
    3.98 -        for n in nodes:
    3.99 -            if not self.is_immediate_user(n):
   3.100 -                return 0
   3.101 -        return 1
   3.102 -
   3.103 -    # Temporary storage administration.
   3.104 -
   3.105 -    def get_temp(self):
   3.106 -
   3.107 -        """
   3.108 -        Add a temporary storage instruction for the current value and return a
   3.109 -        sequence of access instructions.
   3.110 -        """
   3.111 -
   3.112 -        position_in_frame = self.reserve_temp()
   3.113 -        self.new_op(StoreTemp(position_in_frame))
   3.114 -        return LoadTemp(position_in_frame)
   3.115 -
   3.116 -    def reserve_temp(self, temp_position=None):
   3.117 -
   3.118 -        """
   3.119 -        Reserve a new temporary storage position, or if the optional
   3.120 -        'temp_position' is specified, ensure that this particular position is
   3.121 -        reserved.
   3.122 -        """
   3.123 -
   3.124 -        if temp_position is not None:
   3.125 -            pass
   3.126 -        elif not self.temp_positions:
   3.127 -            temp_position = 0
   3.128 -        else:
   3.129 -            temp_position = max(self.temp_positions) + 1
   3.130 -
   3.131 -        self.temp_positions.add(temp_position)
   3.132 -        self.max_temp_position = max(self.max_temp_position, temp_position)
   3.133 -        return self.unit.all_local_usage + temp_position # position in frame
   3.134 -
   3.135 -    def ensure_temp(self, instruction=None):
   3.136 -
   3.137 -        """
   3.138 -        Ensure that the 'instruction' is using a reserved temporary storage
   3.139 -        position.
   3.140 -        """
   3.141 -
   3.142 -        if isinstance(instruction, LoadTemp):
   3.143 -            temp_position = instruction.attr - self.unit.all_local_usage
   3.144 -            self.reserve_temp(temp_position)
   3.145 -
   3.146 -    def discard_temp(self, instruction=None):
   3.147 -
   3.148 -        "Discard any temporary storage position used by 'instruction'."
   3.149 -
   3.150 -        if isinstance(instruction, LoadTemp):
   3.151 -            temp_position = instruction.attr - self.unit.all_local_usage
   3.152 -            self.free_temp(temp_position)
   3.153 -
   3.154 -    def free_temp(self, temp_position):
   3.155 -
   3.156 -        "Free the temporary storage position specified by 'temp_position'."
   3.157 -
   3.158 -        if temp_position in self.temp_positions:
   3.159 -            self.temp_positions.remove(temp_position)
   3.160 -
   3.161 -    def set_frame_usage(self, node, extend):
   3.162 -
   3.163 -        """
   3.164 -        Ensure that the frame usage for the unit associated with 'node' is set
   3.165 -        on the 'extend' instruction.
   3.166 -        """
   3.167 -
   3.168 -        # Remove any ExtendFrame instructions which do nothing.
   3.169 -
   3.170 -        if self.last_op() is extend:
   3.171 -            self.remove_op()
   3.172 -            return
   3.173 -
   3.174 -        ntemp = self.max_temp_position + 1
   3.175 -        extend.attr = ntemp + node.unit.local_usage # NOTE: See get_code for similar code.
   3.176 -
   3.177 -    # Code writing methods.
   3.178 -
   3.179 -    def new_op(self, op):
   3.180 -
   3.181 -        """
   3.182 -        Add 'op' to the generated code, returning a true value if an instruction
   3.183 -        was added.
   3.184 -        """
   3.185 -
   3.186 -        # Optimise load operations employed by this instruction.
   3.187 -
   3.188 -        self.optimiser.optimise_load_operations(op)
   3.189 -        if self.optimiser.optimise_away_no_operations(op) or self.optimiser.optimise_unused_handlers(op):
   3.190 -            return 0
   3.191 -
   3.192 -        # Add the operation to the current block.
   3.193 -
   3.194 -        self.blocks[-1].code.append(op)
   3.195 -        self.optimiser.set_new(op)
   3.196 -        return 1
   3.197 -
   3.198 -    def remove_op(self):
   3.199 -
   3.200 -        "Remove the last instruction."
   3.201 -
   3.202 -        op = self.blocks[-1].code.pop()
   3.203 -        self.optimiser.clear_active()
   3.204 -
   3.205 -    def replace_op(self, op):
   3.206 -
   3.207 -        "Replace the last added instruction with 'op'."
   3.208 -
   3.209 -        self.remove_op()
   3.210 -        self.new_op(op)
   3.211 -
   3.212 -    def replace_active_value(self, op):
   3.213 -
   3.214 -        """
   3.215 -        Replace the value-providing active instruction with 'op' if appropriate.
   3.216 -        """
   3.217 -
   3.218 -        self.optimiser.remove_active_value()
   3.219 -        self.new_op(op)
   3.220 -
   3.221 -    def last_op(self):
   3.222 -
   3.223 -        "Return the last added instruction."
   3.224 -
   3.225 -        try:
   3.226 -            return self.blocks[-1].code[-1]
   3.227 -        except IndexError:
   3.228 -            return None
   3.229 -
   3.230      # Common methods.
   3.231  
   3.232      def _generateGuards(self, node):
   3.233 @@ -1479,11 +1256,20 @@
   3.234          for i, n in enumerate(node.nodes):
   3.235              self.dispatch(n)
   3.236              self.record_value()
   3.237 -            self.new_op(temp)
   3.238 -            self.new_op(StoreAttr(Attr(i + offset, None, None)))
   3.239 -            self.set_source()
   3.240 +            self._storeInSequence(temp, i, offset)
   3.241              self.discard_value()
   3.242  
   3.243 +    def _storeInSequence(self, temp, i, offset=0):
   3.244 +
   3.245 +        """
   3.246 +        Store the current active value in the fragment referenced by 'temp' at
   3.247 +        position 'i' with the given starting 'offset'.
   3.248 +        """
   3.249 +
   3.250 +        self.new_op(temp)
   3.251 +        self.new_op(StoreAttr(Attr(i + offset, None, None)))
   3.252 +        self.set_source()
   3.253 +
   3.254      def _generateTestBoolean(self, node, temp):
   3.255  
   3.256          """