Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@1571: #
andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
andrej@1571: #
andrej@1571: # See COPYING file for copyrights details.
andrej@1571: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
andrej@1571: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
andrej@1571: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Laurent@814: 
andrej@1853: 
andrej@1853: from __future__ import absolute_import
andrej@1732: import re
andrej@1832: from collections import OrderedDict
andrej@2456: from functools import reduce
andrej@1832: 
andrej@1853: from plcopen.plcopen import LoadProject
andrej@1853: from plcopen.definitions import *
Edouard@1390: 
Edouard@1390: TypeHierarchy = dict(TypeHierarchy_list)
Edouard@1390: 
andrej@1736: 
Edouard@1390: def IsOfType(type, reference):
andrej@1736:     """
andrej@1736:     Returns true if the given data type is the same that "reference" meta-type or one of its types.
andrej@1736:     """
Edouard@1390:     if reference is None:
Edouard@1390:         return True
Edouard@1390:     elif type == reference:
Edouard@1390:         return True
Edouard@1390:     else:
Edouard@1390:         parent_type = TypeHierarchy[type]
Edouard@1390:         if parent_type is not None:
Edouard@1390:             return IsOfType(parent_type, reference)
Edouard@1390:     return False
Edouard@1390: 
andrej@1736: 
Edouard@1390: def GetSubTypes(type):
andrej@1736:     """
andrej@1736:     Returns list of all types that correspont to the ANY* meta type
andrej@1736:     """
andrej@1847:     return [typename for typename, _parenttype in TypeHierarchy.items() if not typename.startswith("ANY") and IsOfType(typename, type)]
Edouard@1390: 
andrej@1749: 
Edouard@1390: DataTypeRange = dict(DataTypeRange_list)
Edouard@1390: 
Edouard@1390: """
Edouard@1390: Ordered list of common Function Blocks defined in the IEC 61131-3
Laurent@814: Each block have this attributes:
Laurent@814:     - "name" : The block name
Laurent@814:     - "type" : The block type. It can be "function", "functionBlock" or "program"
Laurent@814:     - "extensible" : Boolean that define if the block is extensible
Laurent@814:     - "inputs" : List of the block inputs
Laurent@814:     - "outputs" : List of the block outputs
Laurent@814:     - "comment" : Comment that will be displayed in the block popup
Laurent@814:     - "generate" : Method that generator will call for generating ST block code
Laurent@814: Inputs and outputs are a tuple of characteristics that are in order:
Laurent@814:     - The name
Laurent@814:     - The data type
Laurent@814:     - The default modifier which can be "none", "negated", "rising" or "falling"
Laurent@814: """
Laurent@814: 
andrej@1739: StdBlckLibs = {libname: LoadProject(tc6fname)[0]
andrej@1768:                for libname, tc6fname in StdTC6Libs}
andrej@1739: StdBlckLst = [{"name": libname, "list":
Edouard@1390:                [GetBlockInfos(pous) for pous in lib.getpous()]}
andrej@1768:               for libname, lib in StdBlckLibs.iteritems()]
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                             Test identifier
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@965: IDENTIFIER_MODEL = re.compile(
Laurent@965:     "(?:%(letter)s|_(?:%(letter)s|%(digit)s))(?:_?(?:%(letter)s|%(digit)s))*$" %
Laurent@965:     {"letter": "[a-zA-Z]", "digit": "[0-9]"})
Laurent@814: 
andrej@1736: 
Laurent@814: def TestIdentifier(identifier):
andrej@1736:     """
andrej@1736:     Test if identifier is valid
andrej@1736:     """
andrej@1736:     return IDENTIFIER_MODEL.match(identifier) is not None
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                        Standard functions list generation
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
Laurent@814: def csv_file_to_table(file):
andrej@1736:     """
andrej@1736:     take a .csv file and translate it it a "csv_table"
andrej@1736:     """
andrej@2448:     table = [[column.strip()
andrej@2448:               for column in line.split(';')]
andrej@2448:              for line in file.readlines()]
andrej@2448:     return table
Laurent@814: 
andrej@1736: 
Laurent@814: def find_section(section_name, table):
andrej@1736:     """
andrej@1736:     seek into the csv table to a section ( section_name match 1st field )
andrej@1736:     return the matching row without first field
andrej@1736:     """
Laurent@814:     fields = [None]
andrej@1828:     while fields[0] != section_name:
Laurent@814:         fields = table.pop(0)
Laurent@814:     return fields[1:]
Laurent@814: 
andrej@1736: 
Laurent@814: def get_standard_funtions_input_variables(table):
andrej@1736:     """
andrej@1736:     extract the standard functions standard parameter names and types...
andrej@1736:     return a { ParameterName: Type, ...}
andrej@1736:     """
Laurent@814:     variables = find_section("Standard_functions_variables_types", table)
Laurent@814:     standard_funtions_input_variables = {}
andrej@1740:     fields = [True, True]
andrej@1828:     while fields[1]:
Laurent@814:         fields = table.pop(0)
andrej@1742:         variable_from_csv = dict([(champ, val) for champ, val in zip(variables, fields[1:]) if champ != ''])
Laurent@814:         standard_funtions_input_variables[variable_from_csv['name']] = variable_from_csv['type']
Laurent@814:     return standard_funtions_input_variables
andrej@1730: 
andrej@1736: 
Laurent@814: def csv_input_translate(str_decl, variables, base):
andrej@1736:     """
andrej@1736:     translate .csv file input declaration into PLCOpenEditor interessting values
andrej@1736:     in : "(ANY_NUM, ANY_NUM)" and { ParameterName: Type, ...}
andrej@1736:     return [("IN1","ANY_NUM","none"),("IN2","ANY_NUM","none")]
andrej@1736:     """
andrej@1740:     decl = str_decl.replace('(', '').replace(')', '').replace(' ', '').split(',')
Laurent@814:     params = []
andrej@1730: 
Laurent@814:     len_of_not_predifined_variable = len([True for param_type in decl if param_type not in variables])
andrej@1730: 
Laurent@814:     for param_type in decl:
Laurent@814:         if param_type in variables.keys():
Laurent@814:             param_name = param_type
Laurent@814:             param_type = variables[param_type]
Laurent@814:         elif len_of_not_predifined_variable > 1:
andrej@1734:             param_name = "IN%d" % base
Laurent@814:             base += 1
Laurent@814:         else:
Laurent@814:             param_name = "IN"
Laurent@814:         params.append((param_name, param_type, "none"))
Laurent@814:     return params
Laurent@814: 
Laurent@814: 
andrej@1736: def get_standard_funtions(table):
andrej@1736:     """
andrej@1736:     Returns this kind of declaration for all standard functions
Laurent@814: 
andrej@1730:             [{"name" : "Numerical", 'list': [   {
Laurent@814:                 'baseinputnumber': 1,
Laurent@814:                 'comment': 'Addition',
Laurent@814:                 'extensible': True,
Laurent@814:                 'inputs': [   ('IN1', 'ANY_NUM', 'none'),
Laurent@814:                               ('IN2', 'ANY_NUM', 'none')],
Laurent@814:                 'name': 'ADD',
Laurent@814:                 'outputs': [('OUT', 'ANY_NUM', 'none')],
Laurent@814:                 'type': 'function'}, ...... ] },.....]
andrej@1736:     """
andrej@1730: 
Laurent@814:     variables = get_standard_funtions_input_variables(table)
andrej@1730: 
andrej@1740:     fonctions = find_section("Standard_functions_type", table)
Laurent@814: 
Laurent@814:     Standard_Functions_Decl = []
Laurent@814:     Current_section = None
andrej@1730: 
Laurent@814:     translate = {
andrej@1878:         "extensible": lambda x: {"yes": True, "no": False}[x],
andrej@1878:         "inputs": lambda x: csv_input_translate(x, variables, baseinputnumber),
andrej@1878:         "outputs": lambda x: [("OUT", x, "none")]}
andrej@1730: 
Laurent@814:     for fields in table:
Laurent@814:         if fields[1]:
Laurent@814:             # If function section name given
Laurent@814:             if fields[0]:
Laurent@814:                 words = fields[0].split('"')
Laurent@814:                 if len(words) > 1:
Laurent@814:                     section_name = words[1]
Laurent@814:                 else:
Laurent@814:                     section_name = fields[0]
andrej@1739:                 Current_section = {"name": section_name, "list": []}
Laurent@814:                 Standard_Functions_Decl.append(Current_section)
Laurent@814:             if Current_section:
Laurent@814:                 Function_decl = dict([(champ, val) for champ, val in zip(fonctions, fields[1:]) if champ])
andrej@1740:                 baseinputnumber = int(Function_decl.get("baseinputnumber", 1))
Laurent@814:                 Function_decl["baseinputnumber"] = baseinputnumber
Laurent@814:                 for param, value in Function_decl.iteritems():
Laurent@814:                     if param in translate:
Laurent@814:                         Function_decl[param] = translate[param](value)
Laurent@814:                 Function_decl["type"] = "function"
andrej@1730: 
andrej@1739:                 if Function_decl["name"].startswith('*') or Function_decl["name"].endswith('*'):
Laurent@814:                     input_ovrloading_types = GetSubTypes(Function_decl["inputs"][0][1])
Laurent@814:                     output_types = GetSubTypes(Function_decl["outputs"][0][1])
Laurent@814:                 else:
Laurent@814:                     input_ovrloading_types = [None]
Laurent@814:                     output_types = [None]
andrej@1730: 
Laurent@814:                 funcdeclname_orig = Function_decl["name"]
Laurent@814:                 funcdeclname = Function_decl["name"].strip('*_')
Laurent@814:                 fdc = Function_decl["inputs"][:]
Laurent@814:                 for intype in input_ovrloading_types:
andrej@1743:                     if intype is not None:
Laurent@814:                         Function_decl["inputs"] = []
Laurent@814:                         for decl_tpl in fdc:
Laurent@814:                             if IsOfType(intype, decl_tpl[1]):
Laurent@814:                                 Function_decl["inputs"] += [(decl_tpl[0], intype, decl_tpl[2])]
Laurent@814:                             else:
Laurent@814:                                 Function_decl["inputs"] += [(decl_tpl)]
andrej@1730: 
Laurent@814:                             if funcdeclname_orig.startswith('*'):
andrej@1730:                                 funcdeclin = intype + '_' + funcdeclname
Laurent@814:                             else:
Laurent@814:                                 funcdeclin = funcdeclname
Laurent@814:                     else:
Laurent@814:                         funcdeclin = funcdeclname
andrej@1730: 
Laurent@814:                     for outype in output_types:
andrej@1743:                         if outype is not None:
Laurent@814:                             decl_tpl = Function_decl["outputs"][0]
andrej@1747:                             Function_decl["outputs"] = [(decl_tpl[0], outype,  decl_tpl[2])]
Laurent@814:                             if funcdeclname_orig.endswith('*'):
andrej@1758:                                 funcdeclout = funcdeclin + '_' + outype
Laurent@814:                             else:
andrej@1758:                                 funcdeclout = funcdeclin
Laurent@814:                         else:
andrej@1758:                             funcdeclout = funcdeclin
Laurent@814:                         Function_decl["name"] = funcdeclout
Laurent@814: 
Edouard@1390:                         # apply filter given in "filter" column
Edouard@1390:                         filter_name = Function_decl["filter"]
Edouard@1390:                         store = True
andrej@1740:                         for (InTypes, OutTypes) in ANY_TO_ANY_FILTERS.get(filter_name, []):
andrej@1740:                             outs = reduce(lambda a, b: a or b,
andrej@1768:                                           map(lambda testtype: IsOfType(
andrej@1768:                                               Function_decl["outputs"][0][1],
andrej@1768:                                               testtype), OutTypes))
andrej@1740:                             inps = reduce(lambda a, b: a or b,
andrej@1768:                                           map(lambda testtype: IsOfType(
andrej@1768:                                               Function_decl["inputs"][0][1],
andrej@1768:                                               testtype), InTypes))
Edouard@1390:                             if inps and outs and Function_decl["outputs"][0][1] != Function_decl["inputs"][0][1]:
Edouard@1390:                                 store = True
Edouard@1390:                                 break
Edouard@1390:                             else:
Edouard@1390:                                 store = False
andrej@1739:                         if store:
Laurent@814:                             # create the copy of decl dict to be appended to section
Laurent@814:                             Function_decl_copy = Function_decl.copy()
Laurent@814:                             Current_section["list"].append(Function_decl_copy)
Laurent@814:             else:
andrej@2453:                 raise ValueError("First function must be in a category")
andrej@1730: 
Laurent@814:     return Standard_Functions_Decl
Laurent@814: 
andrej@1749: 
Edouard@1390: StdBlckLst.extend(get_standard_funtions(csv_file_to_table(open(StdFuncsCSV))))
Edouard@1283: 
Edouard@1283: # Dictionary to speedup block type fetching by name
Laurent@1320: StdBlckDct = OrderedDict()
Edouard@1283: 
Edouard@1283: for section in StdBlckLst:
Laurent@814:     for desc in section["list"]:
Laurent@814:         words = desc["comment"].split('"')
Laurent@814:         if len(words) > 1:
Laurent@814:             desc["comment"] = words[1]
andrej@1730:         desc["usage"] = ("\n (%s) => (%s)" %
andrej@1768:                          (", ".join(["%s:%s" % (input[1], input[0])
andrej@1768:                                      for input in desc["inputs"]]),
andrej@1768:                           ", ".join(["%s:%s" % (output[1], output[0])
andrej@1768:                                      for output in desc["outputs"]])))
andrej@1740:         BlkLst = StdBlckDct.setdefault(desc["name"], [])
Edouard@1283:         BlkLst.append((section["name"], desc))
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                            Languages Keywords
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: # Keywords for Pou Declaration
Laurent@814: POU_BLOCK_START_KEYWORDS = ["FUNCTION", "FUNCTION_BLOCK", "PROGRAM"]
Laurent@814: POU_BLOCK_END_KEYWORDS = ["END_FUNCTION", "END_FUNCTION_BLOCK", "END_PROGRAM"]
Laurent@814: POU_KEYWORDS = ["EN", "ENO", "F_EDGE", "R_EDGE"] + POU_BLOCK_START_KEYWORDS + POU_BLOCK_END_KEYWORDS
Edouard@1283: for category in StdBlckLst:
Laurent@814:     for block in category["list"]:
Laurent@814:         if block["name"] not in POU_KEYWORDS:
Laurent@814:             POU_KEYWORDS.append(block["name"])
Laurent@814: 
Laurent@814: 
Laurent@814: # Keywords for Type Declaration
Laurent@814: TYPE_BLOCK_START_KEYWORDS = ["TYPE", "STRUCT"]
Laurent@814: TYPE_BLOCK_END_KEYWORDS = ["END_TYPE", "END_STRUCT"]
Laurent@814: TYPE_KEYWORDS = ["ARRAY", "OF", "T", "D", "TIME_OF_DAY", "DATE_AND_TIME"] + TYPE_BLOCK_START_KEYWORDS + TYPE_BLOCK_END_KEYWORDS
Laurent@814: TYPE_KEYWORDS.extend([keyword for keyword in TypeHierarchy.keys() if keyword not in TYPE_KEYWORDS])
Laurent@814: 
Laurent@814: 
Laurent@814: # Keywords for Variable Declaration
Laurent@814: VAR_BLOCK_START_KEYWORDS = ["VAR", "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR_EXTERNAL"]
Laurent@814: VAR_BLOCK_END_KEYWORDS = ["END_VAR"]
Laurent@814: VAR_KEYWORDS = ["AT", "CONSTANT", "RETAIN", "NON_RETAIN"] + VAR_BLOCK_START_KEYWORDS + VAR_BLOCK_END_KEYWORDS
Laurent@814: 
Laurent@814: 
Laurent@814: # Keywords for Configuration Declaration
Laurent@814: CONFIG_BLOCK_START_KEYWORDS = ["CONFIGURATION", "RESOURCE", "VAR_ACCESS", "VAR_CONFIG", "VAR_GLOBAL"]
Laurent@814: CONFIG_BLOCK_END_KEYWORDS = ["END_CONFIGURATION", "END_RESOURCE", "END_VAR"]
Laurent@814: CONFIG_KEYWORDS = ["ON", "PROGRAM", "WITH", "READ_ONLY", "READ_WRITE", "TASK"] + CONFIG_BLOCK_START_KEYWORDS + CONFIG_BLOCK_END_KEYWORDS
Laurent@814: 
Laurent@814: # Keywords for Structured Function Chart
Laurent@814: SFC_BLOCK_START_KEYWORDS = ["ACTION", "INITIAL_STEP", "STEP", "TRANSITION"]
Laurent@814: SFC_BLOCK_END_KEYWORDS = ["END_ACTION", "END_STEP", "END_TRANSITION"]
Laurent@949: SFC_KEYWORDS = ["FROM", "TO"] + SFC_BLOCK_START_KEYWORDS + SFC_BLOCK_END_KEYWORDS
Laurent@814: 
Laurent@814: 
Laurent@814: # Keywords for Instruction List
andrej@1768: IL_KEYWORDS = [
andrej@1768:     "TRUE", "FALSE", "LD", "LDN", "ST", "STN", "S", "R", "AND", "ANDN", "OR", "ORN",
andrej@1768:     "XOR", "XORN", "NOT", "ADD", "SUB", "MUL", "DIV", "MOD", "GT", "GE", "EQ", "NE",
andrej@1768:     "LE", "LT", "JMP", "JMPC", "JMPCN", "CAL", "CALC", "CALCN", "RET", "RETC", "RETCN"
andrej@1768: ]
Laurent@814: 
Laurent@814: 
Laurent@814: # Keywords for Structured Text
Laurent@814: ST_BLOCK_START_KEYWORDS = ["IF", "ELSIF", "ELSE", "CASE", "FOR", "WHILE", "REPEAT"]
Laurent@814: ST_BLOCK_END_KEYWORDS = ["END_IF", "END_CASE", "END_FOR", "END_WHILE", "END_REPEAT"]
andrej@1768: ST_KEYWORDS = [
andrej@1768:     "TRUE", "FALSE", "THEN", "OF", "TO", "BY", "DO", "DO", "UNTIL", "EXIT",
andrej@1768:     "RETURN", "NOT", "MOD", "AND", "XOR", "OR"
andrej@1768: ] + ST_BLOCK_START_KEYWORDS + ST_BLOCK_END_KEYWORDS
Laurent@814: 
Laurent@814: # All the keywords of IEC
Laurent@814: IEC_BLOCK_START_KEYWORDS = []
Laurent@814: IEC_BLOCK_END_KEYWORDS = []
Laurent@814: IEC_KEYWORDS = ["E", "TRUE", "FALSE"]
Laurent@814: for all_keywords, keywords_list in [(IEC_BLOCK_START_KEYWORDS, [POU_BLOCK_START_KEYWORDS, TYPE_BLOCK_START_KEYWORDS,
Laurent@814:                                                                 VAR_BLOCK_START_KEYWORDS, CONFIG_BLOCK_START_KEYWORDS,
Laurent@814:                                                                 SFC_BLOCK_START_KEYWORDS, ST_BLOCK_START_KEYWORDS]),
Laurent@814:                                     (IEC_BLOCK_END_KEYWORDS, [POU_BLOCK_END_KEYWORDS, TYPE_BLOCK_END_KEYWORDS,
Laurent@814:                                                               VAR_BLOCK_END_KEYWORDS, CONFIG_BLOCK_END_KEYWORDS,
Laurent@814:                                                               SFC_BLOCK_END_KEYWORDS, ST_BLOCK_END_KEYWORDS]),
Laurent@814:                                     (IEC_KEYWORDS, [POU_KEYWORDS, TYPE_KEYWORDS, VAR_KEYWORDS, CONFIG_KEYWORDS,
Laurent@814:                                                     SFC_KEYWORDS, IL_KEYWORDS, ST_KEYWORDS])]:
Laurent@814:     for keywords in keywords_list:
Laurent@814:         all_keywords.extend([keyword for keyword in keywords if keyword not in all_keywords])