# -*- coding: utf-8 -*-
"""**GRAIL support.**
GRAIL formats support. This is an auxiliary module that sould be imported by
fa.py
.. *Authors:* Rogério Reis & Nelma Moreira
.. *This is part of FAdo project* http://fado.dcc.fc.up.pt
.. versionadded:: 0.9.4
.. *Copyright:* 2011-2014 Rogério Reis & Nelma Moreira {rvr,nam}@dcc.fc.up.pt
.. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with this program; if not,
write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA."""
#__package__="FAdo"
import os
import os.path
import subprocess
from yappy_parser import Yappy, grules
from fa import NFA, DFA
from fl import DFCA
import common
class GrailCommandError(common.fnhException):
"""Error in the argument of a Grail call"""
pass
[docs]class ParserGrail(Yappy):
"""A parser form GRAIL standard automata descriptions
.. inheritance-diagram:: ParserGrail"""
def __init__(self, no_table=1, table='.tableGrail'):
grammar = grules([("r -> f EOL r", self.defaultSemRule),
("r -> i EOL r", self.defaultSemRule),
("r -> t EOL r", self.defaultSemRule),
("r -> j EOL r", self.defaultSemRule),
("r -> ", self.emptySemRule),
("j -> IId equal integer", self.eqSemRule),
("IId -> id", self.defaultSemRule),
("IId -> integer", self.defaultSemRule),
("f -> IId final", self.finalSemRule),
("i -> start IId", self.initialSemRule),
("t -> IId IId IId", self.automataTransitionSemRule)])
tokenizer = [("\n+", lambda x: ("EOL", "EOL")),
("\s+", ""),
("lambda[A-Za-z0-9]+", lambda x: ("id", x)),
("lambda", lambda x: ("id", "@epsilon")),
("\(START\)", lambda x: ("start", "start")),
("\(FINAL\)", lambda x: ("final", "final")),
("\|-", ""),
("-\|", ""),
("=", lambda x: ("equal", "equal")),
("[0-9]+", lambda x: ("integer", x)),
("[A-Za-z][A-Za-z0-9]*", lambda x: ("id", x))]
self.initials = set()
self.finals = set()
self.symbols = set()
self.transitions = {}
self.states = set()
self.eq = {}
Yappy.__init__(self, tokenizer, grammar, table, no_table)
def defaultSemRule(self, lst, context=None):
""" do nothing
:param lst: """
return lst[0]
def emptySemRule(self, lst, context=None):
""" ignore
:param lst:
:param context: """
return []
def finalSemRule(self, lst, context=None):
""" final states
:param context:
:param lst: """
self.states.add(lst[0])
self.finals.add(lst[0])
def initialSemRule(self, lst, context=None):
"""initial states
:param context:
:param lst: """
self.states.add(lst[1])
self.initials.add(lst[1])
def eqSemRule(self, lst, context=None):
"""
:param lst:
:param context: """
self.eq[lst[0]] = int(lst[2])
def automataTransitionSemRule(self, lst, context=None):
""" add a tranasition
:param context:
:param lst: """
self.states.add(lst[0])
self.states.add(lst[2])
self.symbols.add(lst[1])
if lst[0] not in self.transitions.keys():
self.transitions[lst[0]] = {}
if lst[1] not in self.transitions[lst[0]].keys():
self.transitions[lst[0]][lst[1]] = set()
self.transitions[lst[0]][lst[1]].add(lst[2])
# noinspection PyUnresolvedReferences
def getAutomata(self):
""" deal with the information collected"""
isDeterministic = True
if len(self.initials) > 1 or "@epsilon" in self.states:
isDeterministic = False
else:
for s in self.transitions:
for c in self.transitions[s]:
if len(self.transitions[s][c]) > 1:
isDeterministic = False
break
if not isDeterministic:
break
if isDeterministic:
if "l" in self.eq.keys():
fa = DFCA()
fa.setLength = self.eq["l"]
else:
fa = DFA()
else:
fa = NFA()
for s in self.states:
fa.addState(s)
fa.setFinal(fa.indexList(self.finals))
if isDeterministic:
fa.setInitial(fa.stateIndex(common.uSet(self.initials)))
for s1 in self.transitions:
for c in self.transitions[s1]:
fa.addTransition(fa.stateIndex(s1), c,
fa.stateIndex(common.uSet(self.transitions[s1][c])))
else:
fa.setInitial(fa.indexList(self.initials))
for s1 in self.transitions:
for c in self.transitions[s1]:
for s2 in fa.indexList(self.transitions[s1][c]):
fa.addTransition(fa.stateIndex(s1), c, s2)
return fa
[docs]def exportToGrail(fileName, fa):
""" Saves a finite automatom definition to a file using Grail format
:arg fileName: file name
:type fileName: string
:arg fa: the FA
:type fa: FA"""
try:
f = open(fileName, "w")
except IOError:
raise common.DFAerror()
FAToGrail(f, fa)
f.close()
[docs]def FAToGrail(f, fa):
"""Saves a finite automatom definition to an open file using Grail format
:arg f: opended file
:type f: file
:arg fa: the FA
:type fa: FA"""
for s in fa.initialSet():
f.write("(START) |- %s\n" % s)
for s in range(len(fa.States)):
if s in fa.delta:
for a in fa.delta[s].keys():
if isinstance(fa.delta[s][a], set):
for s1 in fa.delta[s][a]:
f.write("%s %s %s\n" % (s, a, s1))
else:
f.write("%s %s %s\n" % (s, a, fa.delta[s][a]))
for s in fa.Final:
f.write("%s -| (FINAL)\n" % s)
[docs]def importFromGrailFile(fileName):
"""Imports a finite automaton from a file in GRAIL format
The type of the object returned depends on the transition definiion red as well as the number of initial states
declared
:arg str fileName: file name
:returns: the automata red
:rtype: FA"""
parser = ParserGrail()
parser.inputfile(fileName)
return parser.getAutomata()
def importFromGrailString(st):
"""Imports a finite automaton from a string in GRAIL format
The type of the object returned depends on the transition definiion red as well as the number of initial states
declared
:arg str st: fstring
:returns: the automata red
:rtype: FA"""
parser = ParserGrail()
parser.input(st)
return parser.getAutomata()
[docs]def FAFromGrail(buffer):
"""Imports a finite automaton from a buffer in GRAIL format
The type of the object returned depends on the transition definiion red as well as the number of initial states
declared
:arg str buffer: buffer file
:returns: the automata red
:rtype: FA"""
parser = ParserGrail()
parser.input(buffer)
return parser.getAutomata()
# noinspection PyUnboundLocalVariable
[docs]class Grail(object):
"""A class for Grail execution"""
def __init__(self):
"""
.. versionchanged:: 0.9.8 tries to initialise execPath from fadorc """
self.syntaxe = {"afacaten": ["afa", "afa", "afa"],
"afacomp": ["afa", "afa"],
"afaexec": ["afa", "word", "bool"],
"afainter": ["afa", "afa", "afa"],
"afareverse": ["afa", "afa"],
"afasize": ["afa", "int"],
"afastar": ["afa", "afa"],
"afatofm": ["afa", "fa"],
"afaunion": ["afa", "afa", "afa"],
"dfaunion": ["fa", "fa", "fa"],
"flappend": ["fl", "word", "fl"],
"flexec": ["fl", "word", "bool"],
"flfilter": ["fl", "fa", "fl"],
"fllq": ["fl", "word", "fl"],
"flprepen": ["fl", "word", "fl"],
"flprod": ["fl", "fl", "fl"],
"flrevers": ["fl", "fl"],
"flrq": ["fl", "word", "fl"],
"fltofm": ["fl", "fa"],
"fltore": ["fl", "re"],
"flunion": ["fl", "fl", "fl"],
"fmcat": ["fa", "fa", "fa"],
"fmcment": ["fa", "fa"],
"fmcomp": ["fa", "fa"],
"fmcross": ["fa", "fa", "fa"],
"fmdeterm": ["fa", "fa"],
"fmenum": ["fa", "fl"],
"fmexec": ["fa", "word", "bool"],
"fmmin": ["fa", "fa"],
"fmminrev": ["fa", "fa"],
"fmplus": ["fa", "fa"],
"fmreach": ["fa", "fa"],
"fmrenum": ["fa", "fa"],
"fmrevers": ["fa", "fa"],
"fmsize": ["fa", "int"],
"fmstar": ["fa", "fa"],
"fmstats": ["fa", "blurb"],
"fmtoafa": ["fa", "afa"],
"fmtofcm": ["fa", "ca"],
"fmtofcm0": ["fa", "ca"],
"fmtofcm2": ["fa", "ca"],
"fmtofl": ["fa", "fl"],
"fmtore": ["fa", "re"],
"fmunion": ["fa", "fa", "fa"],
"iscomp": ["fa", "bool"],
"isdeterm": ["fa", "bool"],
"isempty": ["re", "bool"],
"isnull": ["re", "bool"],
"isomorph": ["fa", "fa", "bool"],
"isuniv": ["fa", "bool"],
"liteafa": ["fa", "blurb"],
"recat": ["re", "re", "re"],
"remin": ["re", "re"],
"restar": ["re", "re"],
"retofl": ["re", "fl"],
"retofm": ["re", "fa"],
"reunion": ["re", "re", "re"]
}
if os.path.isfile("fadorc.py"):
try: from fadorc import grailPath
except ImportError:
return
self.execPath = os.path.abspath(grailPath)
[docs] def setExecPath(self, path):
"""Sets the path to the Grail executables
:arg str path: the path to Grail executables"""
self.execPath = os.path.abspath(path)
[docs] def do(self, cmd, *args):
"""Execute Grail command
:arg cmd: name of the command
:type cmd: string
:arg args: arguments
:raises GrailCommandError: if the syntax is not correct an exception is raised
:raise FAdoGeneralError: if Grail fails to execute something"""
try:
lsargs = self.syntaxe[cmd]
except KeyError:
raise GrailCommandError()
if len(lsargs) != (len(args) + 1):
raise GrailCommandError()
cmdl = [os.path.join(self.execPath, cmd)]
if len(lsargs) > 2: # more than 2 args mean that input is handled by files and output by pipes
largs = []
for i, s in enumerate(lsargs[:-1]):
largs.append(self._processArg(s, args[i]))
for s in largs:
cmdl.append(s)
if lsargs[-1] != "bool":
try:
process = subprocess.Popen(cmdl, stdout=subprocess.PIPE)
except:
raise common.FAdoGeneralError("Grail execution error")
self._cleanFiles(lsargs, largs)
else:
result = True
try:
# TODO: something is wrong with this value outf check it!
subprocess.check_call(cmdl, stdout=outf)
except subprocess.CalledProcessError:
result = False
else: # 2 args mean that everithing can be done by pipes
if lsargs[-1] == "bool":
result = True
try:
subprocess.check_call(cmdl)
except subprocess.CalledProcessError:
result = False
else:
try:
process = subprocess.Popen(cmdl, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
except:
raise common.FAdoGeneralError("Grail execution error")
self._processArgPipe(process.stdin, lsargs[0], args[0])
# output processing
if lsargs[-1] != "bool":
result = self._parseResult(process.communicate(), lsargs[-1])
return result
@staticmethod
def _cleanFiles(lsargs, largs):
for i, s in enumerate(lsargs[:-1]):
if s in ["fa"]:
os.remove(largs[i])
@staticmethod
def _parseResult(pipe, aType):
if aType == "fa" or aType == "ca":
return FAFromGrail(pipe[0])
if aType == "fl":
return pipe[0].split('\n')
if aType == "re":
return pipe[0][:-1]
def _processArgPipe(self, pipe, aType, aObject):
if aType == "fa":
FAToGrail(pipe, aObject)
if aType == "re":
pipe.write(aObject.__str__)
pipe.write("\n")
if aType == "fl":
for wrd in aObject:
pipe.write(wrd)
pipe.write("\n")
if aType == "word":
pipe.write(aObject)
def _processArg(self, aType, aObject):
if aType == "fa":
fname = common.tmpFileName()
exportToGrail(fname, aObject)
return fname
if aType == "re":
fname = common.tmpFileName()
fo = open(fname, "w")
fo.write(aObject.__str__)
fo.write("\n")
fo.close()
return fname
if aType == "fl":
fname = common.tmpFileName()
fo = open(fname, "w")
for wrd in aObject:
fo.write(wrd)
fo.write("\n")
fo.close()
return fname
if aType == "word":
wrd = ""
for s in aObject:
wrd += s
return '"%s"' % wrd