"""Auxiliary functions for writing tables into C program"""

from __future__ import absolute_import, division, print_function
from __future__ import  unicode_literals


import types
import sys
import re
import os
import warnings
import itertools
import ast
from io import IOBase
from operator import __or__, __xor__, __and__
from numbers import Integral
from collections.abc import Iterable
from functools import partial
import numpy

import regex

if sys.version_info[0] >= 3:
    file = IOBase 
    long = int    



#######################################################################
#######################################################################
# Functions provided for the user of the code generator
#######################################################################
#######################################################################

#######################################################################
# Auxiliary functions
#######################################################################

def isinteger(x):
    """Return True if x is an integer"""
    return  isinstance(x, (Integral, numpy.integer))


#######################################################################
# Formatting a python object for use in a C table
#######################################################################


int_format_dict = {8 : (2,""), 16:(4,""), 32:(8,"L"), 64:(16,"LL")}
#  32:(8,"L") means: format "uint32" contains 8 hex digits and suffix "L"

m_int_format = re.compile(r"(u?)int([0-9]+)")

def format_item(n, format = None):
    """Format a python object ``n`` to a C string for use in a C table

    The default format is to apply function str() to the object.

    The current version supports integer formats only.
    Parameter ``format`` must be one of the following strings:

     ``'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'``
    """
    if not format:
        s = str(n)
        if isinteger(n) and s[-1]=='L':
            s = s[:-1]
        return s
    m =  m_int_format.match(format)
    if m:
        blen =  int(m.group(2))
        n = int(n) 
        try:
            len, suffix = int_format_dict[blen]
        except:
            raise ValueError("Bad integer format " + str(format))
        if m.group(1) == 'u':
            n &=  (1 << blen) - 1
            if suffix: suffix = "U" + suffix
            return "0x{0:0{1}x}{2}".format(n , len, suffix)
        else:
            return  "{1}0x{0:0{2}x}{3}".format(abs(n),"-"[n>=0:], len, suffix)
    raise ValueError("Bad format " + str(format))



#######################################################################
# Support for user-defined directives
#######################################################################

def prepend_blanks(string, length):
    """Prepend ``length`` blank characters to each line of a ``string``

    Here parameter ``string`` is a string that may consist of
    several lines.
    """
    def prepend_blank_line(line, length):
        return " " * length + line if len(line.strip()) else line
    l = [prepend_blank_line(line, length) for line in string.split("\n")]
    return "\n".join(l)




class UserDirective(object):
    """Convert a function for use in a code generator directive

    Each code generator directive corresponds to a function. Evaluating
    that function with the arguments given to the directive results in
    a string that makes up the code generated by the directive.

    The list of arguments of a code generator directive is necessarily 
    a string. Usually, this string is converted to a python expression
    which is evaluated using the dictionary 'tables' given to the code
    generator. Here dictionary ``tables`` contains the local variables 
    used by the evaluation process. 

    The constructor of this class returns an object which is a callable 
    function. Calling that function has the same effect as calling the
    function ``f``, which is the first argument passed to the 
    constructor.

    Additional information for dealing with the arguments of the
    function may be given the constructor. This information is used
    by the code generator if an instance of this class is registered
    at the code generator as a directive. For registering a directive
    in that way you may simply enter an instance of this class as a 
    value in the dictionary *directives* passed to the constructor of 
    class ``TableGenerator``.

    Using this feature you may automatically convert arguments of the 
    function e.g. to strings, integers or python objects. You may also 
    prepend a local variable from dictionary ``tables`` to the list 
    of user-specified arguments of the directive. The latter feature 
    gives a directive the flavour of a member function of a class, 
    similar to prepending ``self`` to the list of user-specified 
    arguments.

    :param f:
   
        A callable function which is executed when the code generator
        parses a user-defined directive. The arguments of such a 
        directive are passed to function ``f`` as arguments.

    :param eval_mode:

        This  is a string specifying the syntax of the arguments.
        The i-th character of the string ``eval_mode`` controls the 
        evaluation of the i-th argument of function ``f`` as follows:

          * ``'p'``   Evaluate the argument to a python object (default).
          * ``'i'``   Evaluate the argument to an integer.
          * ``'s'``   Evaluate the argument to a string, ignoring dictionary

          It ``eval_mode`` is ``'.'`` then a single parameter containing 
          just  the given string is passed to function ``f`` as a single 
          argument without any parsing with python.
 
    :param prefix:

        Describes to an optional local variable of the code generator
        prepended to the list of arguments of the directive. It may be
        one of the following:

         *  ``False``:    Nothing is prepended
         *  ``True``:     The dictionary of all local variables is prepended
         * any string:    The local variable with that name is prepended 

    """
    def __init__(self, f, eval_mode = "", prefix = False):
        """Create an instance of class UserDirective for function f 

        Calling that instance with a single argument s of type str has  
        essentially the same effect as calling f with the arguments 
        obtained by  evaluating the string s as a python expression. 
        
        When  registering that instance as a code generator directive,
        the other arguments have the following effect: 

        Remark:
          A dictionary d of local variables provided by the code generator 
          always contains an entry 'NAMES' with  d["NAMES"] = d.  
        """
        if callable(f):
            self.function = f
        else:
            raise TypeError("Function registered for code generator is not callable")          
        self.eval_mode = eval_mode
        if not prefix:
            self.prefixes = ()
        elif isinstance(prefix, str):
            self.prefixes = (prefix,) 
        elif prefix == True:
            self.prefixes = ("NAMES",) 
        else:
             raise TypeError("Prefix must be True, False or a string")
    def set_code_generator(self, code_generator):
        self.tg = code_generator
    def __call__(self, args, *_):
        indent = max(0, self.tg.current_line.find("/"))
        f_args = [self.tg.names[name] for name in self.prefixes]
        f_args += eval_codegen_args(self.tg.names, args, self.eval_mode)
        res = str(self.function(*f_args))
        return prepend_blanks(res, indent),  ""

#######################################################################
# Support for user-defined formatting functions
#######################################################################



class UserFormat(UserDirective):
    r"""Convert a function for string formatting in the code generator

    The code generator evaluates expressions in curly braces ``%{...}``
    in the source code with the python ``.format()`` function for
    stings.

    It is desirable that ``%{int:2*3}`` evaluates to a string 
    representing the integer constant ``int(2*3)`` for some built-in 
    and user-defined functions such as function ``int``.

    Therefore we need a python object with name ``int`` that behaves 
    like the function ``int()`` when being called. That object should 
    also have a ``__format__`` method that returns 
    ``str(int(<expression>))``
    if a string representing a valid python expression is passed to
    the ``__format__`` method.

    ``UserFormat(int)`` creates such an object. Thus:: 

       tables = {"int": UserFormat(int)}
       "{int:2*3}".format(**tables))

    evaluates to ``str(int(2*3))``. Of course, function ``int`` may be
    replaced by any user-defined function.

    Things get more interesting if we prepare a dictionary for the code 
    generator::
    
      from mmgroup.generate_c import TableGenerator, UserFormat
      # Prepare tables for a code generator
      tables = {"int": UserFormat(int),  "a": 3, "b":5}
      # Create a code generator 'codegen'
      codegen = TableGenerator(tables)
      # Make a sample source line of C code
      sample_source = "  x = %{int: a * b + int('1')};\n"
      # generate file "test.c" containing the evaluated sample line
      codegen.generate(sample_source, "test.c")

    Here the string ``{int: a * b + int('1')}`` in the sample source 
    line is formatted with the standard python ``.format`` method.
    The ``.format`` method obtains the entries of the dictionary 
    ``tables`` as keyword arguments.

    So ``%{int: a * b + int('1')}`` evaluates to ``str(3*5+1)``,
    and the C file ``"test.c"`` will contain the line::

      x = 16;


    So ``UserFormat(f)`` creates an object the behaves like ``f`` when 
    it is called; and it returns ``str(f(arg1,arg2,...))`` when it is 
    invoked  in ``"{f:arg1,arg2,...}".format(f = f)``.

    Here the argument list ``arg1, arg2,...`` is processed in according 
    to the same rules as the argument list of a user-defined directive
    in class ``UserDirective``.

    :param f:
   
        A callable function which is executed when the code generator
        parses a user-defined formatting string in curly braces 
        preceded by a '%' character, such as ``%{...}``. 
        The arguments given in the formatting string
        are passed to function ``f`` as arguments.

    :param eval_mode:

        This  is a string specifying the syntax of the arguments in the
        same way as in class ``UserFunction``.
 
    :param prefix:

        Describes to an optional local variable of the code generator
        prepended to the list of arguments of the function in the
        same way as in class ``UserFunction``.

    :param fmt:

        By default, function str() is applied to the result of 
        function ``f`` in order to convert that result to a string.
        This behaviour can be modified by setting parameter ``fmt``.

        if ``fmt`` is a callable function  then ``str(f(*args))`` is 
        replaced by  ``fmt(f(*args))``. 

    :param arg_fmt:

        If this parameter is set then the corresponding ``.format`` 
        method returns ``arg_fmt(*args)`` instead of ``str(f(*args))``.
        This overwrites the parameter ``fmt``.
    """
    def __init__(self, f, eval_mode = "", prefix = False, fmt = None, arg_fmt = None):
        """Create an instance of class UserDirective for function f 

        Calling that instance has the same effect as calling f, except
        that the given arguments for f may be prepended by the another
        argument as specified by  parameter 'prefix' in the 
        constructor. 

        Here parameter 'prefix' specifies an optional prefix to
        be prepended to the list of arguments passed to function f
        as in Class UserDirective.  

        The instance of class UserDirective created here als has a
        __format__method that returns str(f(<expression>))  if a string 
        representing a valid python expression is passed to the 
        __format__ method. Here the expression string is evaluated
        using parameter 'eval_mode' and prepended by an optional
        argument as specified by parameter 'prefix' exactly in the 
        same way as in class UserFormat.

        By default, function str() is applied to the result of 
        function f in order to convert that result to a string.
        This behaviour can be modified by setting one of the
        parameters 'fmt' or 'arg_fmt'.

        if 'fmt' is set then str(f(*args)) is replaced by
        fmt(f(*args)). If 'arg_fmt'  is set then str(f(*args)) 
        is replaced by arg_fmt(*args).
        """
        super(UserFormat, self).__init__(f, eval_mode, prefix) 
        if not arg_fmt is None:
            self.format_function =  arg_fmt
        else:
            if fmt is None: fmt = str
            def fmt_function(*args, **kwds):
               return fmt(f(*args, **kwds))
            self.format_function =  fmt_function
    def __call__(self, *args):
        f_args = [self.tg.names[name] for name in self.prefixes]
        f_args += args
        return self.function(*f_args)
    def __format__(self, format_):
        args = [self.tg.names[name] for name in self.prefixes]
        #print("FORMAT", ":",  format_, ";", self.eval_mode, self.prefixes)
        #print("NAMES", list(self.tg.names.keys()))
        args += eval_codegen_args(self.tg.names, format_, self.eval_mode)
        return self.format_function(*args)



#######################################################################
# Built-in functions 
#######################################################################

# Here is a dictionary of built-in functions that are considered safe
safe_locals = {
    'abs': abs, 'max': max, 'min':min,  'str': str,
    'len': len, 'pow': pow, "int": int, 'range':range,
    'zip': zip,
}



#######################################################################
# Built-in formatting functions
#######################################################################


# Auxiliary functions used in built-in format functions

def strip_L(f):
    """Return a function converting an integer to a string 

    The resulting function uses function f (which should be hex or 
    str) for conversion. Some problems with python 2 are resolved.
    """
    def wrapper(*args, **kwds):
        result = f(*args, **kwds)
        while result[-1:] == 'L': result = result[:-1]
        return result
    return wrapper    


def join_(str_, iterable):
    """functional version of method join of class str

    """
    return str_.join(map(str,iterable))


# Here is the list of built-in format functions
built_in_formats = {
       "int" : UserFormat(int, fmt = strip_L(str)),
       "hex" : UserFormat(lambda x: x, fmt = strip_L(hex)),
       "len" : UserFormat(len, fmt = strip_L(str)),
       "str" : UserFormat(str),
       "join": UserFormat(join_),
}

# Add safe_locals to built_in_formats
for name, f in safe_locals.items():
    if not name in built_in_formats:
        built_in_formats[name] = f

#######################################################################
#######################################################################
# Functions for internal use by the code generator
#######################################################################
#######################################################################






#######################################################################
# Evaluating a string to a python expression using python module ast
#######################################################################


# For background on module ast (abstact syntax tree) see e.g.:
# https://greentreesnakes.readthedocs.io/en/latest/tofrom.html







# Set of nodes in module .ast considered as safe for function eval()
# See  https://greentreesnakes.readthedocs.io/en/latest/nodes.html
good_ast_nodes = set([
   ast.Expression,
   ast.Num, ast.Str, ast.List, ast.Ellipsis, ast.Expr,
   ast.Load,
   ast.Constant,
   ast.Tuple,

   ast.UnaryOp,
   ast.UAdd, ast.USub, ast.Not, ast.Invert,

   ast.BinOp,
   ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, ast.Mod, 
   ast.Pow, ast.LShift, ast.RShift, ast.BitOr, ast.BitXor, 
   ast.BitAnd,

   ast.BoolOp,
   ast.And, ast.Or,

   ast.Compare,
   ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE, ast.Is, 
   ast.IsNot, ast.In, ast.NotIn,

   ast.Call,
   ast.Subscript, ast.Index, ast.Slice, ast.ExtSlice, 
   ast.ListComp, ast.comprehension
])



try:
    good_ast_nodes.add(ast.NameConstant) # New in version 3.4
except:
    pass 


class EvalNodeVisitor(ast.NodeVisitor):
    """Node visitor for Abstract Syntax Trees creeated by module  .ast 

    This vistor checks for all nodes of an AST whether they are secure 
    for processing with function eval(). Method visit() raises TypeError
    if an insecure node id found in the AST.

    Caution: 
    Leading underscores in python identifiers or attributes are illegal! 

    Note that leading underscores in an identifier or an attribute are
    considered dangerous. E.g.  parsing '().__class__.__bases__[0]'
    yields the class <object>, which a user should not manipulate. For  
    a discussion about the security of eval() see:
    https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html

    Creating tuples in backets or dictionaries in curly brackets is also
    illegal because the syntax gets messy when used in the code generator.
    """
    def visit(self, Node):
        cls = Node.__class__
        #print("visiting", Node, cls)
        if cls in good_ast_nodes:
            pass
        elif cls == ast.Name:
            if Node.id[:1] == "_":
                raise NameError("Illegal leading '_' in name in python expression") 
        elif cls == ast.Attribute:
            if Node.attr[:1] == "_":
                raise NameError("Illegal leading '_' in name in python expression") 
        else:
            print("\nIllegal ast node", type(Node))
            raise TypeError("Illegal construct in python expression") 
        self.generic_visit(Node)


def safe_eval(s,  locals_ = {}):
    """An attempt to create a secure version of function eval()

    See function EvalNodeVisitor() for allowd python contructs.
    """
    a_tree = ast.parse(s.strip(), mode="eval")
    vars_ = {}
    vars_.update(safe_locals)
    vars_.update(locals_)
    EvalNodeVisitor().visit(a_tree)
    code = compile(a_tree, filename="Python expression", mode="eval")
    result = eval(code, {'__builtins__': {}},  vars_ )
    return result


 
   

def ast_untuple(expression):
    """Evaluate string 'expression' to a list of AST nodes.

    If 'expression'is a tuple, a list of AST nodes is returned,
    such that each entry of the list represents an entry of the
    tuple. Otherwise a list with one entry representing the
    'expression' is returned.  
    """
    tree = ast.parse(expression.strip(), mode = "eval")
    if isinstance(tree.body, ast.Tuple):
        arglist = []
        for node in tree.body.elts:
            node = ast.fix_missing_locations(node)
            expr = ast.Expression(node)
            expr = ast.fix_missing_locations(expr)
            arglist.append(expr)   
    else:
        arglist = [tree]
    for arg in arglist:
        EvalNodeVisitor().visit(arg) 
    return arglist
    





#######################################################################
# Evaluating a string to a list of arguments
#######################################################################



def ast_eval_str(node, locals_ = None):
    """Evaluate an AST expression to a string"""
    if isinstance(node.body, ast.Name):
        return node.body.id
    EvalNodeVisitor().visit(node)
    code = compile(node, filename="Python parameter", mode="eval")
    result = str(eval(code, {'__builtins__': {}},  {} ))
    return result
   
    
def ast_eval_pyobj(node, locals_):
    """Evaluate an AST expression to a python object"""
    EvalNodeVisitor().visit(node)
    code = compile(node, filename="Python parameter", mode="eval")
    return eval(code, {'__builtins__': {}},  locals_ )

def ast_eval_int(node,  locals_):
    """Evaluate an AST expression to an int"""
    return int(ast_eval_pyobj(node, locals_))


ast_eval_mode = {
   'p': ast_eval_pyobj,   # evaluate argument to a python object
   'i': ast_eval_int,     # evaluate argument to an int
   's': ast_eval_str,     # evaluate argument to a string
}





   

def eval_codegen_args(tables, expression, eval_mode = ""):
    """Evaluate expression for a user-defined function

    'expression' must be a valid python expression, which will be
    converted to a list of arguments for a user-defined function.

    If 'expression' is not a tuple, a list containing just that
    (evaluated) expression is returned. Otherwise each entry of 
    the tuple is evaluated and the list of those evaluated
    expressions is returned

    'tables' is a list of local variables used for evaluating the
    expression. 

    The i-th character of the string 'eval_mode' controls the
    evaluation of the i-th argument as follows:

      'p'   Evaluate the argument to a python object (default).
      'i'   Evaluate the argument to an integer.
      's'   Evaluate the argument to a string, ignoring dictionary

    It 'eval_mode' is "." then a list containing just the string
    "expression" is returned.
    """
    assert isinstance(expression, str), expression
    #print("eval_codegen_args", tables, expression)
    if eval_mode[:1] == ".":
        return [expression]
    ast_par_list = ast_untuple(expression)
    d = len(ast_par_list)  - len(eval_mode) 
    if d > 0:
        eval_mode += "p" * d
    args = [ ast_eval_mode[fmt](ast_param, tables)
           for (ast_param, fmt) in zip(ast_par_list,  eval_mode)]
    return args
   




#######################################################################
# Updating a dictionary with local variables in FOR directive
#######################################################################

class NotInDict:
    """Models a value for a non-existent key in a dictionary"""
    pass


class SaveDictFor(object):
    """Models shadowing local variables in a FOR directive

    The directive

        // %%FOR <local> in <iterable>

    has the effect that local variables in the list <local> are 
    iteratively set to the values obtained by iterating through
    <iterable>. This shadows the original values of the variables
    with the same names, if present.
    
    If <local> contains only one variable, that variable is set to
    the values yielded by the <iterable>. If <local> is a comma-
    separated list several variables, then <iterable> must yield 
    tuples of exactly the same length as the list <local>, and 
    all local variables are updated accordingly for each tuple
    yielded by the <iterable>. 

    More sophisticated forms of list comprehensions in a FOR
    directive are not supported.

    This constructor of this class takes the dictionary 'dict'
    of current  variables and the argument 'local', which may be a 
    single variable name or an tuple of variable names. It saves
    all items of the dictionary with a key equal to any
    of these names.
   
    Method 'set_local' updates the names all local variables in
    the dictionary using values from an 'entry' onvtained from 
    the <interable>.

    Method 'restore' restores the dictionary of current variables
    to state before calling the constructor.
    """
    def __init__(self, dict, local):
        """Create instance for updating a dictionary of variables

        'dict' is dictionary of variables to be updated.
        'local' is a name of a local variable to be updated;
        it may also be a tuple of such names.
        """
        self.local = local
        self.dict = dict
        self.old_dict = {}
        if not isinstance(local, tuple):
            local = (local,)
        for name in local:
            try: 
                self.old_dict[name] = dict[name]
            except KeyError:
                self.old_dict[name] = NotInDict

    def set_local(self, entry):
        """Set keys in dictionary to values given by 'entry'

        Here 'entry' is an item yielded from the <iterative> in the
        <expression> of the FOR directive. The dictionary of 
        current variables given in the constructor is updated
        apropriately.
        """ 
        if not isinstance(self.local, tuple):
            self.dict[self.local] = entry
        else:
            if len(entry) != len(self.local):
                raise ValueError("Too many or few values to unpack in FOR directive")
            for local, value in zip(self.local, entry):
                self.dict[local] = value
         
    def restore(self):
        """Restore the dictionary passed in the constructor"""
        for name, value in self.old_dict.items():
            if value is NotInDict:
                try:
                    del self.dict[name]
                except:
                    pass
            else:
                self.dict[name] = value
       

         


#######################################################################
# Support for built-in directives of the code generator
#######################################################################


 
def eval_codegen_table(tables, expression):
    """Evaluate 'expression' for table generation

    Here expression is evaluated as a pair (table, format_) where
    'table' is an iterable python object and 'format_' is an optional
    string (default is the empty string).

    'tables' is a list of local variables used for evaluating the
    expression. 

    The function returns a triple (name, table, format_). If parameter
    'table' is a python identifier, then 'name' is set to the name of
    that identifer, otherwise 'name' is set to the empty string.
    """ 
    ast_par_list = ast_untuple(expression)
    node = ast_par_list[0]
    name =  node.body.id if isinstance(node.body, ast.Name) else ""
    table = ast_eval_pyobj(node, tables)
    format_ = ast_eval_str(ast_par_list[1], {}) if len(ast_par_list) > 1 else ""
    return name, table, format_ 
    
    

codegen_assign_match = re.compile(r"\s*([A-Za-z]\w*)\s*=\s*(.+)$")
 
def eval_codegen_assign(tables, expression):
    """Evaluate 'expression' in an assignment function

    Here 'expression' is evaluated must be a string of the form 

        <name> = <value>

    and <value> is evaluated to a python object <py_value>. 

    'tables' is a list of local variables used for evaluating the
    expression. 

    The function returns the pair (<name>, <py_value>).
    """ 
    try:
        name, value = codegen_assign_match.match(expression).groups()
    except:
        raise TypeError("Syntax error in assignment in code generator")   
    return name, safe_eval(value, tables)
    
 


def eval_codegen_m(tables, expression, match_pattern, name):
    """Evaluate 'expression' in a 'FOR' or "WITH" directive.

    Here 'expression'  must "match_pattern" which is of shape 

        '<name> in <value>'    or '<name> = <value>' 

    and <value> is evaluated to a python object <py_value> which 
    should be iterable. 

    'tables' is a list of local variables used for evaluating the
    expression. 

    The function returns the pair (<name>, <py_value>).
    """ 
    try:
        names, value = match_pattern.match(expression).groups()
        names = tuple(s.strip() for s in names.split(","))
        for s in names: 
            assert s[:1].isalpha()
        assert len(names) > 0
        if len(names) == 1: 
            names = names[0]
    except:
        err = "Syntax error in %d directive in code generator"
        raise TypeError(err % name) 
    return names, safe_eval(value, tables)

codegen_assign_for = re.compile(r"\s*([,\w\s]+)\s+in\s+(.+)$")

def eval_codegen_for(tables, expression):
    """Evaluate 'expression' in a 'FOR' directive.

    Here 'expression'  must be a string of the form 

        <name> in <value>

    and <value> is evaluated to a python object <py_value> which 
    should be iterable. 

    'tables' is a list of local variables used for evaluating the
    expression. 

    The function returns the pair (<name>, <py_value>).
    """ 
    return eval_codegen_m(tables, expression, codegen_assign_for, "FOR")

codegen_assign_with = re.compile(r"\s*([,\w\s]+)=(.+)$")

def eval_codegen_with(tables, expression):
    """Evaluate 'expression' in a 'WITH' directive.

    Here 'expression'  must be a string of the form 

        <name> = <value>

    and <value> is evaluated to a python object  <py_value>. If 
    <name> is a tuple of names then <value> should evaluate to
    a tuple <py_value> of the same length. 

    The function returns the pair (<name>, <py_value>). Operation
    is similar to function eval_codegen_for().
    """ 
    return eval_codegen_m(tables, expression, 
        codegen_assign_with, "WITH")




codegen_assign_if = re.compile(r"\s*(IF\s+)?(.+)$")


def eval_codegen_else(tables, expression):
    """Evaluate 'expression' in an 'ELSE' directive.

    Here 'expression'  must be a string of the form 

        IF <value>

    where the 'IF' keyword is optional. <value> is evaluated to a 
    Boolean value <bool_value>. 

    'tables' is a list of local variables used for evaluating the
    expression. 

    The function returns the pair (<bool_value>, <if_found>), where
    <if_found> is True iff the expression contains the IF kewyord.
    """ 
    try:
        if_found, condition = codegen_assign_if.match(expression).groups()
    except:
        if len(expression.strip()) == 0:
            return True, False  # empty ELSE expression
        raise TypeError("Syntax error in IF directive in code generator")
    condition = condition.strip()
    condition = safe_eval(condition, tables) if condition else 1
    #  print("ELSE", condition, ";", if_found)
    return  bool(condition),  bool(if_found)

      
m_direct_arg = re.compile(r"\s*([a-zA-Z0-9_]+)(\s|,|$)")

def direct_single_arg(expression, kwd = ""):
    """Evaluate 'expression' to an alphanumeric string.

    The function returns that string.

    'kwd' is a string indicating the keyword for which this function
    has been called. This is used for error reporting.
    """
    try:
        return m_direct_arg.match(expression).groups()[0]
    except:
        raise TypeError("Syntax error in codegen directive %s" % kwd)
    

def eval_codegen_join(tables, expression):
    """Evaluate 'expression' in a 'JOIN' directive.

    Here 'expression'  must be a string of the form 

        <infix> , <suffix>

    Both, <infix> and  <suffix> are evaluated to a string. If any 
    of the evluated strings does no contain a '\n' character, 
    a '\n' is appended to the string.

    'tables' is a list of local variables used for evaluating the
    expression. 

    The function returns a pair (infix, suffix) of strings.
    """ 
    args = eval_codegen_args(tables, expression, "ss")
    while len(args) < 2: 
        args.append("")
    for i in range(2):
        if not "\n" in args[i]:
            args[i] =  args[i] + "\n"
        if (args[i][:1]).isidentifier():
            args[i] = " " + args[i]
        if (args[i][-1:]).isidentifier():
            args[i] = args[i] + " "
    return tuple(args[:2])

#######################################################################
# Evaluating a balanced list of arguments, deprecated!!!
#######################################################################
     


bracket_match = { ")":"(", "]":"[", "}":"{" }


def balanced_arglist(s):
    """Parse a comma-separated string s into a list of strings
    
    Deprecated!

    Semicolons and unbalanced brackets, square bracktes or curly 
    brackets are illegal. The strings "#" and "//" (outside 
    brackets) mark the beginning  of a comment. 

    The function separates the arguments by a comma and returns
    the list of arguments as strings with leading and trailing
    whitespace stripped off.
    """
    assert isinstance(s, str), s
    brackets = []
    args = []
    start, end_ = 0, len(s)
    for i, c in enumerate(s):
        if c in "([{": 
            brackets.append(c)
        elif c in ")]}":
            try:
                assert  brackets.pop() == bracket_match[c]
            except:
                print("\nOpening/closing bracket mismatch in string:")
                print(s)
                raise ValueError("Opening/closing bracket mismatch")
        elif c == "," and not len(brackets):
             args.append(s[start:i].strip())
             start = i+1
        elif c in ";/#" and not len(brackets):
            if c == "#" or s[i:i+2] == "//":
                 end_ = i
                 break
            elif c == ";":
                print("\nIllegal character %s in string:" % c)
                print(s)
                raise ValueError("String format error")
    last = s[start:end_].strip()
    if len(last):
        args.append(last)
    return args










#######################################################################
# Support for generating tables
#######################################################################







def unformat_item(s, format = None):
    """Convert a C literal string to a python object, 

    Deprecated!!!

    This tries to reverse the effect of function format_item.
    """
    if not format:
        return ast.literal_eval(s)
    m =  m_int_format.match(format)
    if m:
        while len(s) and s[-1] in "UL":
            s = s[:-1] 
        return ast.literal_eval(s)
    raise ValueError("Cannot interpret C literal " + str(s))

def sizeof_format(format = None):
    """Return size of entry with of a given format in bytes

    Parameter format is as in function format_item().
    The function returns 0 if the size cannot be determined.
    """
    try:
        m =  m_int_format.match(format)
        return int(m.group(2))/8
    except:
        return 0


def make_table(table, format=None):
    """Convert a python table to a C sequence separated by commas

    Parameter ``table`` may be anything iterable. Function 

      ``format_item(x, format)``

    is applied to each item ``x`` in the table.

    The output may contain several lines.
    """
    l = 77 // (len(format_item(0,format)) + 1)
    while (l & (l-1)): l = l-1
    s = ""
    for i,x in enumerate(table):
        if i % l == 0: s += ""
        s += format_item(x, format) 
        if i < len(table) - 1: s += ","
        if i % l == l-1: s += "\n"
    if s[-1] != "\n": s += "\n"
    return s    


class named_table(object):
    """Helper class for class TableGenerator for modeling a table
     
    self.str() returns the name of the table
    self[i] returns the i-th entry of the table
    """
    def __init__(self, name, table):
        self.name = name
        self.table = table
    def __repr__(self):
        return self.name
    def __getitem__(self, i):
        return self.table.__getitem__(i)
    def __len__(self):
        return len(self.table)
    str = __repr__


#######################################################################
# Suppport for generating doc strings
#######################################################################


def n_leading_spaces(s):
    """Return number of leading spaces of s"""
    return sum(1 for _ in itertools.takewhile(lambda x : x.isspace(), s))
    #6789012345678901234567890123456789012345678901234567890123456789012345


def py_doc_to_comment(s):
    """Convert python __doc__ string to a C comment

    """
    ls = [s1.rstrip() for s1 in str(s).split("\n")]
    while len(ls) and len(ls[-1]) == 0:
        del ls[-1]
    if len(ls) == 0: return ""
    try:
        trim_len = min(n_leading_spaces(s1) for s1 in ls[1:] if len(s1))
    except ValueError:
        trim_len = 0
    ls = ls[:1] + [s2[trim_len:] for s2 in ls[1:]]
    return   "\n".join("// " + s for s in ls) + "\n"



#######################################################################
# Support for formating a C code line
#######################################################################




# The following expression matches  a string  with balanced curly 
# braces and preceded by a '%' character.
mm_line = regex.compile(r"\%(\{(?>[^{}]|(?1))*\})")


def _do_format(line, format_args, format_kwds, terminal, matchobj):
    try:
        return matchobj.group()[1:].format(*format_args, **format_kwds)
    except:
        if not terminal:
            return matchobj.group()
        print("\nError in formatting line:")
        print(line)
        print("Cannot expand expression", matchobj.group() + "\n")
        raise


def format_line(line, dictionary=None, args = (), terminal = True):
    """Format a C code ``line``

    The function scans  a ``line`` for occurences of a string 
    with balanced curly braces and preceded by a '%' character.
    When such a string ``s``  is found, then ``s`` is substituted
    by ``s[1:].format(*args, **dictionary)``.
    
    We consider this to be a reasonable compromise between the 
    standard C syntax an the very powerful pythonic ``.format``
    method for strings.
    """
    if line.find(r"%{") < 0:
        return line
    f_subst = partial(_do_format, line, args, dictionary, terminal)
    try:
        return regex.sub(mm_line, f_subst, line)
    except:
        print("\nError in formatting line:")
        print(s + "\n")
        raise



#######################################################################
# Indentation of a block
#######################################################################



def indent_subsequent_lines(s):
    if "\n" in s[1:-1]:
        lines = s.split("\n")
        index = 0
        while lines[0][index].isspace(): index += 1
        indent = lines[0][:index]
        for  i, s in enumerate(lines[1:]):
            if len(s): lines[i+1] = indent + s
        return "\n".join(lines)
    else:
        return s


#######################################################################
# Support for generating header files
#######################################################################

def line_as_comment(line):
    """Change a C code line to a comment by adding the prefix "// "

    If the last character of the line is a backslash, certain
    characters are appended. This silences some compiler warnings.
    """
    line = "// " + line
    if line[-2:] == "\\\n": 
        line = line[:-2] +  "\\ //\n"
    return line





