<reponame>GeorgeArgyros/symautomata
"""
This module transforms a file containing regular expressions for flex parser
into a DFA object (pyfst automaton)
"""
import os
import random
import re
import string
import tempfile
from subprocess import call
from sys import argv

from alphabet import createalphabet
from dfa import DFA


class Flexparser:
    """
    This class parses compiles a file containing regular
    expressions into a flex compiled file, and then parses
    the flex DFA into a pyfst DFA
    """

    outfile = ""

    def __init__(self, alphabet=None):
        """
        Initialization function
        Args:
            alphabet (list): input alphabet
        Returns:
            None
        """
        if alphabet is not None:
            self.alphabet = alphabet
        else:
            self.alphabet = []

    def _create_automaton_from_regex(self, myfile):
        """
        Generates a flex compiled file using myfile
        as input
        Args:
            myfile (str): Flex file to be parsed
        Returns:
            None
        """
        call(["flex", "-o", self.outfile, "-f", myfile])

    def _read_transitions(self):
        """
        Read DFA transitions from flex compiled file
        Args:
            None
        Returns:
            list: The list of states and the destination for a character
        """
        states = []
        i = 0
        regex = re.compile('[ \t\n\r:,]+')
        found = 0  # For maintaining the state of yy_nxt declaration
        state = 0  # For maintaining the state of opening and closing tag of yy_nxt
        substate = 0  # For maintaining the state of opening and closing tag of each set in yy_nxt
        mapping = []  # For writing each set of yy_next
        cur_line = None
        with open(self.outfile) as flex_file:
            for cur_line in flex_file:
                if cur_line[0:35] == "static yyconst flex_int16_t yy_nxt[" or cur_line[0:33] == "static const flex_int16_t yy_nxt[":
                    found = 1
                    # print 'Found yy_next declaration'
                    continue
                if found == 1:
                    if state == 0 and cur_line[0:5] == "    {":
                        state = 1
                        continue
                    if state == 1 and cur_line[0:7] == "    } ;":
                        state = 0
                        break

                    if substate == 0 and cur_line[0:5] == "    {":
                        mapping = []
                        substate = 1
                        continue
                    if substate == 1:
                        if cur_line[0:6] != "    },":
                            cur_line = "".join(cur_line.split())
                            if cur_line == '':
                                continue
                            if cur_line[cur_line.__len__() - 1] == ',':
                                splitted_line = regex.split(
                                    cur_line[:cur_line.__len__() - 1])
                            else:
                                splitted_line = regex.split(cur_line)
                            mapping = mapping + splitted_line
                            continue
                        else:
                            cleared = []
                            for j in mapping:
                                cleared.append(int(j))
                            states.append(cleared)
                            mapping = []
                            substate = 0

        return states

    def _read_accept_states(self):
        """
        Read DFA accepted states from flex compiled file
        Args:
            None
        Returns:
            list: The list of accepted states
        """
        states = []
        i = 0
        regex = re.compile('[ \t\n\r:,]+')
        found = 0  # For maintaining the state of yy_accept declaration
        state = 0  # For maintaining the state of opening and closing tag of yy_accept
        mapping = [] # For writing each set of yy_accept
        cur_line = None
        with open(self.outfile) as flex_file:
            for cur_line in flex_file:
                if cur_line[0:37] == "static yyconst flex_int16_t yy_accept" or cur_line[0:35] == "static const flex_int16_t yy_accept":
                    found = 1
                    continue
                if found == 1:
                    # print x
                    if state == 0 and cur_line[0:5] == "    {":
                        mapping.append(0)  # there is always a zero there
                        state = 1
                        continue

                    if state == 1:
                        if cur_line[0:7] != "    } ;":
                            cur_line = "".join(cur_line.split())
                            if cur_line == '':
                                continue
                            if cur_line[cur_line.__len__() - 1] == ',':
                                splitted_line = regex.split(
                                    cur_line[:cur_line.__len__() - 1])
                            else:
                                splitted_line = regex.split(cur_line)
                            mapping = mapping + splitted_line
                            continue
                        else:
                            cleared = []
                            for j in mapping:
                                cleared.append(int(j))
                            max_value = max(cleared)
                            for i in range(0, len(cleared)):
                                if cleared[i] > 0 and cleared[
                                        i] < (max_value - 1):
                                    states.append(i)
                            return states
        return []

    def _read_null_transitions(self):
        """
        Read DFA'input_string NULL transitions from flex compiled file
        Args:
            None
        Returns:
            list: The list of state transitions for no character
        """
        states = []
        regex = re.compile('[ \t\n\r:,]+')
        found = 0  # For maintaining the state of yy_NUL_trans declaration
        state = 0  # For maintaining the state of opening and closing tag of yy_NUL_trans
        mapping = []  # For writing each set of yy_NUL_trans
        cur_line = None
        with open(self.outfile) as flex_file:
            for cur_line in flex_file:
                if cur_line[0:len("static yyconst yy_state_type yy_NUL_trans")
                            ] == "static yyconst yy_state_type yy_NUL_trans" or cur_line[0:len("static const yy_state_type yy_NUL_trans")
                            ] == "static const yy_state_type yy_NUL_trans":
                    found = 1
                    # print 'Found yy_next declaration'
                    continue
                if found == 1:
                    if state == 0 and cur_line[0:5] == "    {":
                        mapping.append(0)  # there is always a zero there
                        state = 1
                        continue
                    if state == 1:
                        if cur_line[0:7] != "    } ;":
                            cur_line = "".join(cur_line.split())
                            if cur_line == '':
                                continue
                            if cur_line[cur_line.__len__() - 1] == ',':
                                splitted_line = regex.split(
                                    cur_line[:cur_line.__len__() - 1])
                            else:
                                splitted_line = regex.split(cur_line)
                                #  print y
                            mapping = mapping + splitted_line
                            continue
                        else:
                            cleared = []
                            for j in mapping:
                                cleared.append(int(j))
                            states = cleared
                            mapping = []
                            state = 0

        return states

    def _create_states(self, states_num):
        """
        Args:
            states_num (int): Number of States
        Returns:
            list: An initialized list
        """
        states = []
        for i in range(0, states_num):
            states.append(i)
        return states

    def _add_sink_state(self, states):
        """
        This function adds a sing state in the total states
        Args:
            states (list): The current states
        Returns:
            None
        """
        cleared = []
        for i in range(0, 128):
            cleared.append(-1)
        states.append(cleared)

    def _create_delta(self):
        """
        This function creates the delta transition
        Args:
            startState (int): Initial state of automaton
        Results:
            int, func: A number indicating the total states, and the delta function
        """
        states = self._read_transitions()
        total_states = len(states)
        self._add_sink_state(states)
        nulltrans = self._read_null_transitions()

        def delta(current_state, character):
            """
            Sub function describing the transitions
            Args:
                current_state (str): The current state
                character (str): The input character
            Returns:
                str: The next state
            """
            if character != '':
                newstate = states[current_state][ord(character)]
                if newstate > 0:
                    return newstate
                else:
                    return total_states
            else:
                return nulltrans[current_state]

        return total_states + 1, delta

    def yyparse(self, lexfile):
        """
        Args:
            lexfile (str): Flex file to be parsed
        Returns:
            DFA: A dfa automaton
        """
        temp = tempfile.gettempdir()
        self.outfile = temp+'/'+''.join(
            random.choice(
                string.ascii_uppercase + string.digits) for _ in range(5)) + '_lex.yy.c'
        self._create_automaton_from_regex(lexfile)
        states_num, delta = self._create_delta()
        states = self._create_states(states_num)
        accepted_states = self._read_accept_states()
        if self.alphabet != []:
            alphabet = self.alphabet
        else:
            alphabet = createalphabet()
        mma = DFA(alphabet)
        for state in states:
            if state != 0:
                for char in alphabet:
                    nextstate = delta(state, char)
                    mma.add_arc(state - 1, nextstate - 1, char)
                if state in accepted_states:
                    mma[state - 1].final = True
        if os.path.exists(self.outfile):
            os.remove(self.outfile)
        return mma


def main():
    """
    Testing function for Flex Regular Expressions to FST DFA
    """
    if len(argv) < 2:
        print 'Usage: %s fst_file [optional: save_file]' % argv[0]
        return
    flex_a = Flexparser()
    mma = flex_a.yyparse(argv[1])
    mma.minimize()
    print mma
    if len(argv) == 3:
        mma.save(argv[2])


if __name__ == '__main__':
    main()
