├── .gitignore ├── LICENSE ├── README.md ├── examples └── ex1.L5X ├── l5x2c.py ├── l5xparser.py ├── plcmodel.template ├── requirements.txt ├── runglex.py ├── rungyacc.py └── testgen.py /.gitignore: -------------------------------------------------------------------------------- 1 | ihm 2 | *.pyc 3 | *.c 4 | *.out 5 | parsetab.py 6 | __pycache__ 7 | env 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alair Dias Junior 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # l5x2c 2 | 3 | l5x2c is a [transcompiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) (a source-to-source compiler) written in Python that translates Rockwell's ladder programs exported to a `.L5X` file into a C program with the purpose of verification. It is a **work-in-progress** and, as of now, it supports complex rung structure, the most used ladder instructions (see the [list](#supported-ladder-instructions)), tag definitions, multiple programs and routines. 4 | 5 | l5x2c uses [PLY](http://www.dabeaz.com/ply/) to build a single pass transcompiler that translates the original ladder program into a C code that implements an [Accumulator Machine](https://en.wikipedia.org/wiki/Accumulator_(computing)) that models the behavior of the Rungs. 6 | 7 | 8 | ## Requirements 9 | 10 | To run the Python Scripts, you are going to need [PLY](http://www.dabeaz.com/ply/). If you intend to run the l5x2c test script, you are going to need [CBMC](https://www.cprover.org/cbmc/). We are using CBMC version 5.11. 11 | 12 | ## Usage 13 | 14 | There are 5 python scripts available in the repository: 15 | 16 | * **l5x2c.py** is the main script and is used to translate the `.L5X` file into a C program 17 | * **l5xparser.py** is the script that parses the `.L5X` file into a dictionary 18 | * **runglex.py** is the scanner for the ladder rung. It analyses the rung and issues a stream of tokens 19 | * **rungyacc.py** is the parser and code generator that analyses the sintax of the ladder rung and translates it to the equivalent C code 20 | * **testgen.py** is a script that generate a C file that can be used to verify the behavior of *l5x2c*. The script generates a file `tests/tests.c` that can be verified using CBMC using the command `cbmc tests/tests.c` 21 | 22 | To translate a `.L5X` file into C, just run: 23 | 24 | ```console 25 | python l5x2c.py examples/ex1.L5X examples/ex1.c 26 | ``` 27 | 28 | This will analyse the file in `examples/ex1.L5X` and create the file `examples/ex1.c` with the corresponding C code. 29 | 30 | Single ladder's rung can be translated using `rungyacc.py` as bellow: 31 | 32 | ```console 33 | echo "XIC(A)XIO(B)OTE(C);" | python rungyacc.py 34 | ``` 35 | 36 | This will print a list of commands that use the template functions available in `plcmodel.template` file. 37 | 38 | ## Supported Ladder Instructions 39 | 40 | The following instructions are supported by l5x2c: 41 | 42 | * XIC 43 | * XIO 44 | * ONS 45 | * EQU 46 | * GEQ 47 | * LEQ 48 | * NEQ 49 | * GRT 50 | * LIM 51 | * OTE 52 | * OTU 53 | * OTL 54 | * RES 55 | * MOV 56 | * TON 57 | * TOF 58 | * CTU 59 | * JSR 60 | * ADD 61 | * SUB 62 | * DIV 63 | * CLR 64 | -------------------------------------------------------------------------------- /examples/ex1.L5X: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | DA 2D 04 C0 88 13 00 00 0D 0F 00 00 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 00 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 00 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 00 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 00 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 00 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /l5x2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | ################################################################################ 4 | # Copyright (c) 2019 Alair Dias Junior 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # 25 | # This file is part of l5x2c. To know more about it, acccess: 26 | # https://github.com/alairjunior/l5x2c 27 | # 28 | ################################################################################ 29 | import sys 30 | import logging 31 | import argparse 32 | import traceback 33 | from string import Template 34 | from runglex import runglex 35 | from rungyacc import rungyacc 36 | from l5xparser import l5xparser 37 | 38 | #################################################### 39 | # 40 | # LOOKUP TABLE FOR DATATYPE TRANSLATION 41 | # 42 | ################################################### 43 | datatype_translation_lut = { 44 | 'SINT' : 'int8_t', 45 | 'INT' : 'int16_t', 46 | 'DINT' : 'int32_t', 47 | 'BOOL' : 'bool', 48 | 'BIT' : 'bool', 49 | 'REAL' : 'float', 50 | 'LINT' : 'int64_t', 51 | 'USINT' : 'uint8_t', 52 | 'UINT' : 'unt16_t', 53 | 'UDINT' : 'unt32_t', 54 | 'LREAL' : 'double', 55 | 'ULINT' : 'uint64_t', 56 | 'TIMER' : 'timer', 57 | 'COUNTER': 'counter' 58 | } 59 | 60 | 61 | #################################################### 62 | # 63 | # ADD TEMPLATES TO THE GENERATED FILE 64 | # 65 | ################################################### 66 | def addTemplates(f, parameters): 67 | with open('plcmodel.template', 'r') as t: 68 | text = t.read() 69 | template = Template(text) 70 | f.write(template.substitute(parameters)) 71 | 72 | 73 | 74 | #################################################### 75 | # 76 | # ADD A DATATYPE TO THE GENERATED FILE 77 | # 78 | ################################################### 79 | def addDataType(f, name, datatype): 80 | if name not in datatype_translation_lut: 81 | f.write('\n/* DataType %s */\n' % (name)) 82 | f.write('typedef struct %s_t {\n' % (name)) 83 | for field in datatype['members']: 84 | typename = datatype['members'][field]['type'] 85 | dimension = 0 86 | if 'dimension' in datatype['members'][field]: 87 | dimension = int(datatype['members'][field]['dimension']) 88 | field_type = datatype_translation_lut.get(typename, typename) 89 | if dimension > 0: 90 | f.write('\t%s %s[%d];\n' % (field_type, field, dimension)) 91 | else: 92 | f.write('\t%s %s;\n' % (field_type, field)) 93 | f.write('} %s_t;\n' % (name)) 94 | 95 | #################################################### 96 | # 97 | # ADD DATATYPES TO THE GENERATED FILE 98 | # 99 | ################################################### 100 | def addDataTypes(f, datatypes): 101 | 102 | f.write('\n/***************************************************\n') 103 | f.write('* DataType Definitions *\n') 104 | f.write('***************************************************/\n') 105 | 106 | unprocessed = list(datatypes.keys()) 107 | while len(unprocessed) > 0: 108 | for name in unprocessed: 109 | datatype = datatypes[name] 110 | can_process = True 111 | if 'dependencies' in datatype: 112 | for dependency in datatype['dependencies']: 113 | if dependency in unprocessed: 114 | can_process = False 115 | break 116 | if can_process: 117 | addDataType(f, name, datatype) 118 | unprocessed.remove(name) 119 | break 120 | 121 | #################################################### 122 | # 123 | # GET INITIAL VALUE STRING 124 | # 125 | ################################################### 126 | def get_initial_value(node): 127 | if node['type'] == 'value': 128 | return '=' + node['data']['data'] 129 | elif node['type'] == 'array': 130 | result = " = { " 131 | for index in node['data']['data']: 132 | ending = ', ' if int(index) != int(node['data']['dimensions']) - 1 else '' 133 | result += get_initial_value(node['data']['data'][index]).replace('=','',1) + ending 134 | return result + " }" 135 | elif node['type'] == 'struct': 136 | result = " = { " 137 | ending = '.' 138 | for field in node['data']['data']: 139 | result += (ending + field + get_initial_value(node['data']['data'][field])) 140 | ending = ', .' 141 | return result + ' }' 142 | else: 143 | logging.error("Undefined Tag major type: %s" %(node['type'])) 144 | raise Exception("Undefined Tag major type: %s" %(node['type'])) 145 | 146 | #################################################### 147 | # 148 | # ADD TAGS TO THE GENERATED FILE 149 | # 150 | ################################################### 151 | def addTags(f, tags): 152 | if len(tags) == 0: return 153 | 154 | f.write('\n/***************************************************\n') 155 | f.write('* Tags Definitions *\n') 156 | f.write('***************************************************/\n') 157 | 158 | for tag in tags: 159 | content = tags[tag] 160 | datatype = content['data']['type'] 161 | typename = datatype_translation_lut.get(datatype, datatype + '_t') 162 | f.write('%s %s%s;\n\n' % (typename, tag, get_initial_value(content))) 163 | 164 | #################################################### 165 | # 166 | # PROCESS THE RUNGS 167 | # 168 | ################################################### 169 | def processRungs(f, routine): 170 | # build the lexer 171 | lexer = runglex() 172 | # Build the parser 173 | parser = rungyacc() 174 | for rung in routine: 175 | f.write(" // %s\n" % (rung)) 176 | try: 177 | f.write(" %s\n" % (parser.parse(rung))) 178 | except SyntaxError as e: 179 | f.write("// Syntax Error") 180 | finally: 181 | f.write("\n\n") 182 | 183 | #################################################### 184 | # 185 | # ADD ROUTINE FUNCTION TO THE C FILE 186 | # 187 | ################################################### 188 | def addFunction(f, program, routine, rungs): 189 | f.write("\n/* Function for Routine %s of program %s */\n" % (routine,program)) 190 | f.write("void %s() {\n" % (routine)) 191 | processRungs(f,rungs) 192 | f.write("}\n\n") 193 | 194 | 195 | #################################################### 196 | # 197 | # TRANSLATE THE DICTIONARY TO A C FILE 198 | # 199 | ################################################### 200 | def dict2c(l5x, output, parameters): 201 | with open(output, 'w') as f: 202 | addTemplates(f, parameters) 203 | addDataTypes(f, l5x['datatypes']) 204 | addTags(f, l5x['tags']['Controller']) 205 | f.write('\n/***************************************************\n') 206 | f.write('* Program Definitions *\n') 207 | f.write('***************************************************/\n') 208 | programs = l5x['programs'] 209 | for program in programs: 210 | f.write("\n/* Program %s */\n" % (program)) 211 | if 'Programs' in l5x['tags']: 212 | if program in l5x['tags']['Programs']: 213 | addTags(f, l5x['tags']['Programs'][program]) 214 | routines = programs[program]['routines'] 215 | for routine in routines: 216 | addFunction(f, program, routine, routines[routine]['rungs']) 217 | 218 | 219 | #################################################### 220 | # 221 | # MAIN SCRIPT FOR COMMAND LINE EXECUTION 222 | # 223 | ################################################### 224 | def main(): 225 | log = logging.getLogger('l5x2c') 226 | description = "Converts a Rockwell's L5X file into a C program" 227 | parser = argparse.ArgumentParser(description=description) 228 | parser.add_argument("input") 229 | parser.add_argument("output") 230 | parser.add_argument('-ss', '--stack_size', type=int, default=1000, 231 | help="Stack size for the stack machine") 232 | parser.add_argument('-st', '--scan_time', type=int, default=100, 233 | help="Scan time for the PLC model") 234 | 235 | args = vars(parser.parse_args()) 236 | try: 237 | l5x = l5xparser() 238 | l5x_data = l5x.parse(args['input']) 239 | parameters = { 240 | 'stack_size': args['stack_size'], 241 | 'scan_time': args['scan_time'] 242 | } 243 | dict2c(l5x_data, args['output'], parameters) 244 | except KeyError as e: 245 | log.critical("Key Error: " + str(e)) 246 | traceback.print_exc() 247 | 248 | if __name__== "__main__": 249 | main() 250 | -------------------------------------------------------------------------------- /l5xparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | ################################################################################ 4 | # Copyright (c) 2019 Alair Dias Junior 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # 25 | # This file is part of l5x2c. To know more about it, acccess: 26 | # https://github.com/alairjunior/l5x2c 27 | # 28 | ################################################################################ 29 | import sys 30 | import logging 31 | import argparse 32 | import traceback 33 | from xml.dom.minidom import parse 34 | 35 | class l5xparser(): 36 | #################################################### 37 | # 38 | # CONSTRUCTS FOR THE 'LIST' OPTION 39 | # 40 | ################################################### 41 | 42 | constructs = ['tags', 'programs', 'routines', 'rungs'] 43 | 44 | 45 | #################################################### 46 | # 47 | # PARSE XML FILE USING DOM 48 | # obs: avoids parsing again if already parsed 49 | ################################################### 50 | old_filename = None 51 | old_dom = None 52 | def parse_xml(self, filename): 53 | if filename == self.old_filename: 54 | return self.old_dom 55 | else: 56 | self.old_filename = filename 57 | self.old_dom = parse(filename) 58 | return self.old_dom 59 | 60 | 61 | #################################################### 62 | # 63 | # RETURNS THE LIST OF PROGRAMS IN THE L5X FILE 64 | # 65 | ################################################### 66 | def list_programs(self, args): 67 | dom = self.parse_xml(args['filename']) 68 | program_list = [] 69 | for programs in dom.getElementsByTagName("Programs"): 70 | for program in programs.getElementsByTagName("Program"): 71 | program_list.append(program.getAttribute("Name")) 72 | 73 | return program_list 74 | 75 | 76 | 77 | 78 | #################################################### 79 | # 80 | # RETURNS THE LIST OF ROUTINES IN THE PROGRAM 81 | # 82 | ################################################### 83 | def list_routines(self, args): 84 | program_name = args['program'] 85 | if (program_name is None): 86 | raise Exception("Define the working program to list the routines") 87 | 88 | dom = self.parse_xml(args['filename']) 89 | routine_list = [] 90 | for programs in dom.getElementsByTagName("Programs"): 91 | for program in programs.getElementsByTagName("Program"): 92 | if (program.getAttribute("Name") == program_name): 93 | for routines in program.getElementsByTagName("Routines"): 94 | for routine in routines.getElementsByTagName("Routine"): 95 | routine_list.append(routine.getAttribute("Name")) 96 | 97 | return routine_list 98 | 99 | #################################################### 100 | # 101 | # RETURNS THE LIST OF RUNGS IN THE ROUTINE 102 | # 103 | ################################################### 104 | def list_rungs(self, args): 105 | program_name = args['program'] 106 | if (program_name is None): 107 | raise Exception("Define the working program to list the rungs") 108 | 109 | routine_name = args['routine'] 110 | if (routine_name is None): 111 | raise Exception("Define the working routine to list the rungs") 112 | 113 | dom = self.parse_xml(args['filename']) 114 | rung_list = [] 115 | for programs in dom.getElementsByTagName("Programs"): 116 | for program in programs.getElementsByTagName("Program"): 117 | if (program.getAttribute("Name") == program_name): 118 | for routines in program.getElementsByTagName("Routines"): 119 | for routine in routines.getElementsByTagName("Routine"): 120 | if (routine.getAttribute("Name") == routine_name): 121 | for rll in routine.getElementsByTagName("RLLContent"): 122 | for rung in rll.getElementsByTagName("Rung"): 123 | for text in rung.getElementsByTagName("Text"): 124 | rung_list.append(text.firstChild.wholeText.strip()) 125 | 126 | return rung_list 127 | 128 | #################################################### 129 | # 130 | # BUILD A VALUE MEMBER 131 | # 132 | ################################################### 133 | def build_value_member(self, member): 134 | return { 135 | 'type': member.getAttribute('DataType'), 136 | 'data': member.getAttribute('Value') 137 | } 138 | 139 | #################################################### 140 | # 141 | # PROCESS DATA STRUCTURE 142 | # 143 | ################################################### 144 | def process_data_structure(self, node, tagtype): 145 | entry = None 146 | for content in node.childNodes: 147 | if content.nodeType == content.ELEMENT_NODE: 148 | if content.getAttribute('DataType') == tagtype: 149 | if content.tagName == 'Structure': 150 | entry = {} 151 | entry['type'] = 'struct' 152 | entry['data'] = self.build_structure_member(content) 153 | elif content.tagName == 'DataValue': 154 | entry = {} 155 | entry['type'] = 'value' 156 | entry['data'] = self.build_value_member(content) 157 | elif content.tagName == 'Array': 158 | entry = {} 159 | entry['type'] = 'array' 160 | entry['data'] = self.build_array_member(content) 161 | return entry 162 | 163 | 164 | #################################################### 165 | # 166 | # BUILD AN ARRAY MEMBER 167 | # 168 | ################################################### 169 | def build_array_member(self, member): 170 | 171 | data = {} 172 | datatype = member.getAttribute('DataType') 173 | for element in member.getElementsByTagName('Element'): 174 | index = int(element.getAttribute('Index')[1:-1]) 175 | if element.hasAttribute('Value'): 176 | data[index] = { 177 | 'type': 'value', 178 | 'data': { 179 | 'type': datatype, 180 | 'data': element.getAttribute('Value') 181 | } 182 | } 183 | else: 184 | for structure in element.getElementsByTagName('Structure'): 185 | if structure.getAttribute('DataType') == datatype: 186 | data[index] = { 187 | 'type': 'struct', 188 | 'data': self.build_structure_member(structure) 189 | } 190 | 191 | array = { 192 | 'type': member.getAttribute('DataType'), 193 | 'dimensions': member.getAttribute('Dimensions'), 194 | 'data': data 195 | } 196 | 197 | return array 198 | 199 | #################################################### 200 | # 201 | # BUILD A STRUCTURE MEMBER 202 | # 203 | ################################################### 204 | def build_structure_member(self, member): 205 | 206 | data = {} 207 | for field in member.childNodes: 208 | if field.nodeType == field.ELEMENT_NODE: 209 | fieldname = field.getAttribute('Name') 210 | tagname = field.tagName 211 | if tagname == 'DataValueMember': 212 | data[fieldname] = { 213 | 'type': 'value', 214 | 'data': self.build_value_member(field) 215 | } 216 | elif tagname == 'ArrayMember': 217 | data[fieldname] = { 218 | 'type': 'array', 219 | 'data': self.build_array_member(field) 220 | } 221 | elif tagname == 'StructureMember': 222 | data[fieldname] = { 223 | 'type': 'struct', 224 | 'data': self.build_structure_member(field) 225 | } 226 | else: 227 | logging.warning("Unsupported field type %s. Field %s was ignored" % (tagname,fieldname)) 228 | 229 | 230 | structure = { 231 | 'type': member.getAttribute('DataType'), 232 | 'data': data 233 | } 234 | 235 | return structure 236 | 237 | 238 | #################################################### 239 | # 240 | # RETURN A DICT CONTAINING ALL TAGS 241 | # 242 | ################################################### 243 | def parse_l5x_tags(self, filename): 244 | dom = self.parse_xml(filename) 245 | l5x_tags = {} 246 | 247 | for tags in dom.getElementsByTagName("Tags"): 248 | parent_tag = tags.parentNode.tagName 249 | entry = None 250 | if (parent_tag == 'Controller'): 251 | l5x_tags['Controller'] = {} 252 | entry = l5x_tags['Controller'] 253 | elif (parent_tag == 'Program'): 254 | if not 'Programs' in l5x_tags: 255 | l5x_tags['Programs'] = {} 256 | l5x_tags['Programs'][tags.parentNode.getAttribute('Name')] = {} 257 | entry = l5x_tags['Programs'][tags.parentNode.getAttribute('Name')] 258 | else: 259 | loggin.warning("Unsupported parent tag: %s" % (parent_tag)) 260 | 261 | for tag in tags.getElementsByTagName('Tag'): 262 | tagname = tag.getAttribute('Name') 263 | tagtype = tag.getAttribute('DataType') 264 | tagdata = None 265 | for data in tag.getElementsByTagName('Data'): 266 | if data.hasAttribute('Format'): 267 | if data.getAttribute('Format') == 'Decorated': 268 | tagdata = data 269 | break 270 | 271 | if tagdata is None: 272 | logging.warning("Tag %s has no Decorated Data. Ignored." % (tagname)) 273 | continue 274 | 275 | result = self.process_data_structure(tagdata, tagtype) 276 | 277 | if result is None: 278 | logging.warning("Unsupported tag type %s. Tag %s was ignored." % (tagtype, tagname)) 279 | else: 280 | entry[tagname] = result 281 | 282 | return l5x_tags 283 | 284 | #################################################### 285 | # 286 | # RETURN A DICT CONTAINING ALL DATATYPES 287 | # 288 | ################################################### 289 | def parse_l5x_datatypes(self, filename): 290 | dom = self.parse_xml(filename) 291 | l5x_datatypes = {} 292 | for datatypes in dom.getElementsByTagName('DataTypes'): 293 | for datatype in datatypes.getElementsByTagName('DataType'): 294 | datatype_name = datatype.getAttribute('Name') 295 | l5x_datatypes[datatype_name] = {} 296 | for members in datatype.getElementsByTagName('Members'): 297 | l5x_datatypes[datatype_name]['members'] = {} 298 | members_dict = l5x_datatypes[datatype_name]['members'] 299 | for member in members.getElementsByTagName('Member'): 300 | members_dict[member.getAttribute('Name')] = { 301 | 'type': member.getAttribute('DataType'), 302 | 'dimension': member.getAttribute('Dimension'), 303 | 'radix': member.getAttribute('Radix'), 304 | } 305 | 306 | for dependencies in datatype.getElementsByTagName('Dependencies'): 307 | l5x_datatypes[datatype_name]['dependencies'] = {} 308 | dependencies_dict = l5x_datatypes[datatype_name]['dependencies'] 309 | for dependency in dependencies.getElementsByTagName('Dependency'): 310 | dependencies_dict[dependency.getAttribute('Name')] = { 311 | 'type': dependency.getAttribute('Type'), 312 | } 313 | 314 | return l5x_datatypes 315 | 316 | #################################################### 317 | # 318 | # RETURNS A DICT CONTAINING ALL PROGRAMS 319 | # 320 | ################################################### 321 | def parse(self, filename): 322 | l5x_data = {} 323 | args = {} 324 | args['filename'] = filename 325 | l5x_data['tags'] = self.parse_l5x_tags(filename) 326 | l5x_data['datatypes'] = self.parse_l5x_datatypes(filename) 327 | l5x_data['programs'] = {} 328 | for program_name in self.list_programs(args): 329 | args['program'] = program_name 330 | programs = l5x_data['programs'] 331 | programs[program_name] = {} 332 | program = programs[program_name] 333 | program['routines'] = {} 334 | for routine_name in self.list_routines(args): 335 | args['routine'] = routine_name 336 | routines = program['routines'] 337 | routines[routine_name] = {} 338 | routine = routines[routine_name] 339 | routine['rungs'] = self.list_rungs(args) 340 | return l5x_data 341 | 342 | #################################################### 343 | # 344 | # MAIN SCRIPT FOR COMMAND LINE EXECUTION 345 | # 346 | ################################################### 347 | def main(): 348 | description = "Parses the Rockwell's L5X file" 349 | parser = argparse.ArgumentParser(description=description) 350 | parser.add_argument("filename") 351 | parser.add_argument('-p', '--program', help="define the working program") 352 | parser.add_argument('-r', '--routine', help="define the working program") 353 | parser.add_argument('-L', '--list', dest='construct', 354 | help='print the selected program constructs', 355 | choices=constructs) 356 | 357 | args = vars(parser.parse_args()) 358 | try: 359 | if (args['construct']): 360 | print(globals()["list_"+args['construct']](args)) 361 | else: 362 | l5xparser = l5xparser() 363 | print(l5xparser.parse(args['filename'])['tags']) 364 | except Exception as e: 365 | print(str(e)) 366 | traceback.print_exc() 367 | 368 | if __name__== "__main__": 369 | main() 370 | -------------------------------------------------------------------------------- /plcmodel.template: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2019 Alair Dias Junior 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | * 23 | * This file is part of l5x2c. To know more about it, acccess: 24 | * https://github.com/alairjunior/l5x2c 25 | * 26 | *******************************************************************************/ 27 | 28 | /* This file was generated automatically by l5x2c */ 29 | /* https://github.com/alairjunior/l5x2c */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | /*************************************************** 38 | /* Stack control functions */ 39 | /**************************************************/ 40 | bool stack[${stack_size}] = {false}; 41 | int top = 0; 42 | bool acc() {return stack[top-1];} 43 | void push(bool x) {stack[top++]=x;} 44 | bool pop() {return stack[--top];} 45 | void and() {bool a = pop(); bool b = pop(); push(a && b);} 46 | void or() {bool a = pop(); bool b = pop(); push(a || b);} 47 | void clear(){top=0;} 48 | 49 | /*************************************************** 50 | /* Model functions */ 51 | /**************************************************/ 52 | int get_scan_time(){return ${scan_time};} 53 | 54 | /*************************************************** 55 | /* Timer Structure */ 56 | /**************************************************/ 57 | typedef struct timer { 58 | bool EN; 59 | bool TT; 60 | bool DN; 61 | long int PRE; 62 | long int ACC; 63 | } timer; 64 | 65 | /*************************************************** 66 | /* Timer Functions */ 67 | /**************************************************/ 68 | /* Based on Rockwell's manual */ 69 | void ton(bool acc, timer *t) { 70 | if (!acc) { 71 | t->DN = false; 72 | t->ACC = 0; 73 | t->TT = false; 74 | t->EN = false; 75 | } else { 76 | t->TT = true; 77 | if(t->DN) { 78 | t->TT = false; 79 | t->EN = true; 80 | return; 81 | } else if (!t->EN) { 82 | t->EN = true; 83 | } else { 84 | t->ACC += get_scan_time(); 85 | if (t->ACC < 0) { 86 | t->ACC = 2147483647; 87 | t->TT = false; 88 | t->DN = true; 89 | t->EN = true; 90 | } else if (t->ACC >= t->PRE) { 91 | t->TT = false; 92 | t->DN = true; 93 | t->EN = true; 94 | } 95 | } 96 | } 97 | } 98 | 99 | /* Based on Rockwell's manual */ 100 | void tof(bool acc, timer *t) { 101 | if (acc) { 102 | t->DN = true; 103 | t->ACC = 0; 104 | t->TT = false; 105 | t->EN = true; 106 | } else { 107 | t->TT = true; 108 | if(!t->DN) { 109 | t->TT = false; 110 | t->EN = false; 111 | return; 112 | } else if (t->EN) { 113 | t->EN = false; 114 | } else { 115 | t->ACC += get_scan_time(); 116 | if (t->ACC < 0) { 117 | t->ACC = 2147483647; 118 | t->TT = false; 119 | t->DN = false; 120 | t->EN = false; 121 | } else if (t->ACC >= t->PRE) { 122 | t->TT = false; 123 | t->DN = false; 124 | t->EN = false; 125 | } 126 | } 127 | } 128 | } 129 | 130 | /*************************************************** 131 | /* Counter Structure */ 132 | /**************************************************/ 133 | typedef struct counter { 134 | bool CD; 135 | bool CU; 136 | bool DN; 137 | bool OV; 138 | bool UN; 139 | long int PRE; 140 | long int ACC; 141 | } counter; 142 | 143 | 144 | /*************************************************** 145 | /* Counter Function */ 146 | /**************************************************/ 147 | /* Based on Rockwell's manual */ 148 | void ctu(int acc, counter *c) { 149 | bool ov = false; 150 | if(acc) { 151 | if (!c->CU) { 152 | c->CU = true; 153 | if (c->ACC == 2147483647) { 154 | ov = true; 155 | } 156 | c->ACC += 1; 157 | if (ov) { 158 | if (c->UN) { 159 | c->UN = false; 160 | c->OV = false; 161 | } else { 162 | c->OV = true; 163 | return; 164 | } 165 | } 166 | } 167 | if (!c->UN && !c->OV) { 168 | if (c->ACC < c->PRE) { 169 | c->DN = false; 170 | } else { 171 | c->DN = true; 172 | } 173 | } 174 | } else { 175 | c->CU = false; 176 | if (!c->UN && !c->OV) { 177 | if (c->ACC < c->PRE) { 178 | c->DN = false; 179 | } else { 180 | c->DN = true; 181 | } 182 | } 183 | } 184 | } 185 | 186 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pkg-resources==0.0.0 2 | ply==3.11 3 | -------------------------------------------------------------------------------- /runglex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | ################################################################################ 4 | # Copyright (c) 2019 Alair Dias Junior 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # 25 | # This file is part of l5x2c. To know more about it, acccess: 26 | # https://github.com/alairjunior/l5x2c 27 | # 28 | ################################################################################ 29 | import sys 30 | import logging 31 | from ply import lex 32 | from ply.lex import TOKEN 33 | 34 | # List of token names. 35 | reserved = { 36 | 'XIC' : 'XIC', 37 | 'XIO' : 'XIO', 38 | 'OTE' : 'OTE', 39 | 'OTU' : 'OTU', 40 | 'OTL' : 'OTL', 41 | 'TON' : 'TON', 42 | 'TOF' : 'TOF', 43 | 'ONS' : 'ONS', 44 | 'RES' : 'RES', 45 | 'MOV' : 'MOV', 46 | 'CTU' : 'CTU', 47 | 'EQU' : 'EQU', 48 | 'GEQ' : 'GEQ', 49 | 'NEQ' : 'NEQ', 50 | 'LEQ' : 'LEQ', 51 | 'GRT' : 'GRT', 52 | 'COP' : 'COP', 53 | 'CPT' : 'CPT', 54 | 'ADD' : 'ADD', 55 | 'SUB' : 'SUB', 56 | 'CLR' : 'CLR', 57 | 'LIM' : 'LIM', 58 | 'DIV' : 'DIV', 59 | 'BTD' : 'BTD', 60 | 'JSR' : 'JSR', 61 | 'MSG' : 'MSG', 62 | } 63 | 64 | tokens = [ 65 | 'LPAR', 66 | 'RPAR', 67 | 'LBRA', 68 | 'RBRA', 69 | 'COMMA', 70 | 'SEMICOLON', 71 | 'TAG', 72 | 'UNDEF_VAL', 73 | 'COMM_TAG', 74 | 'CPT_MINUS', 75 | 'CPT_PLUS', 76 | 'CPT_TIMES', 77 | 'CPT_DIV', 78 | 'NUMBER' 79 | ] + list(reserved.values()) 80 | 81 | 82 | 83 | def runglex(debug=False): 84 | log = logging.getLogger('l5x2c') 85 | 86 | # basic regular expressions for creating tokens 87 | ID = r'([a-zA-Z_] ( [a-zA-Z0-9_] )*)' 88 | OBJ_ID = r'(' + ID + '(\.' + ID + ')*)' 89 | INDEX = r'(\[ (([0-9])+ | (' + OBJ_ID + '))\])' 90 | OBJ_INDEX = r'(' + OBJ_ID + '(' + INDEX + ')?)' 91 | TAG = r'(' + OBJ_INDEX + '(\.' + OBJ_INDEX + ')*(\.([0-9])+)?)' 92 | COMM_TAG = r'(' + ID + ':([0-9])+:' + ID + '\.' + TAG + ')' 93 | 94 | # Regular expression rules for simple tokens 95 | t_LPAR = r'\(' 96 | t_RPAR = r'\)' 97 | t_LBRA = r'\[' 98 | t_RBRA = r'\]' 99 | t_COMMA = r',' 100 | t_SEMICOLON = r';' 101 | t_UNDEF_VAL = r'\?' 102 | t_XIC = r'XIC' 103 | t_XIO = r'XIO' 104 | t_OTE = r'OTE' 105 | t_OTU = r'OTU' 106 | t_OTL = r'OTL' 107 | t_TON = r'TON' 108 | t_TOF = r'TOF' 109 | t_ONS = r'ONS' 110 | t_RES = r'RES' 111 | t_MOV = r'MOV' 112 | t_CTU = r'CTU' 113 | t_EQU = r'EQU' 114 | t_GEQ = r'GEQ' 115 | t_NEQ = r'NEQ' 116 | t_LEQ = r'LEQ' 117 | t_GRT = r'GRT' 118 | t_COP = r'COP' 119 | t_CPT = r'CPT' 120 | t_ADD = r'ADD' 121 | t_SUB = r'SUB' 122 | t_CLR = r'CLR' 123 | t_LIM = r'LIM' 124 | t_DIV = r'DIV' 125 | t_BTD = r'BTD' 126 | t_JSR = r'JSR' 127 | t_MSG = r'MSG' 128 | t_CPT_MINUS = r'\-' 129 | t_CPT_PLUS = r'\+' 130 | t_CPT_TIMES = r'\*' 131 | t_CPT_DIV = r'/' 132 | t_NUMBER = r'[0-9]*\.?[0-9]+([eE][\-\+]?[0-9]+)?' 133 | 134 | @TOKEN(COMM_TAG) 135 | def t_COMM_TAG(t): 136 | return t 137 | 138 | 139 | @TOKEN(TAG) 140 | def t_TAG(t): 141 | t.type = reserved.get(t.value,'TAG') # Check for reserved words 142 | return t 143 | 144 | # A string containing ignored characters 145 | t_ignore = ' \t\n\r' 146 | 147 | 148 | # Define a rule so we can track line numbers 149 | def t_newline(t): 150 | r'\n+' 151 | t.lexer.lineno += len(t.value) 152 | 153 | # Error handling rule 154 | def t_error(t): 155 | log.error("Illegal character '%s'" % t.value[0]) 156 | t.lexer.skip(1) 157 | 158 | return lex.lex(debug=debug,errorlog=log) 159 | 160 | #################################################### 161 | # 162 | # MAIN SCRIPT FOR COMMAND LINE EXECUTION 163 | # 164 | ################################################### 165 | def main(): 166 | # Build the lexer 167 | lexer = runglex() 168 | 169 | lexer.input(sys.stdin.readline()) 170 | while True: 171 | tok = lexer.token() 172 | if not tok: 173 | break 174 | print(tok) 175 | 176 | 177 | if __name__== "__main__": 178 | main() 179 | -------------------------------------------------------------------------------- /rungyacc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | ################################################################################ 4 | # Copyright (c) 2019 Alair Dias Junior 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # 25 | # This file is part of l5x2c. To know more about it, acccess: 26 | # https://github.com/alairjunior/l5x2c 27 | # 28 | ################################################################################ 29 | 30 | ################################################################################ 31 | # --- RUNG GRAMMAR --- 32 | # 33 | # 34 | # RUNG : INPUT_LIST OUTPUT_LIST ; 35 | # | OUTPUT_LIST ; 36 | # 37 | # INPUT_LIST : INPUT_INSTRUCTION 38 | # | INPUT_LIST INPUT_INSTRUCTION 39 | # | INPUT_BRANCH 40 | # | INPUT_LIST INPUT_BRANCH 41 | # 42 | # INPUT_BRANCH : [ INPUT_LEVEL ] 43 | # | [] 44 | # 45 | # INPUT_LEVEL : INPUT_LIST , INPUT_LEVEL 46 | # | INPUT_LIST 47 | # | , 48 | # | , INPUT_LEVEL 49 | # 50 | # OUTPUT_LIST : OUTPUT_SEQ 51 | # | OUTPUT_BRANCH 52 | # 53 | # OUTPUT_SEQ : OUTPUT_INSTRUCTION 54 | # | OUTPUT_SEQ OUTPUT_INSTRUCTION 55 | # 56 | # OUTPUT_BRANCH : [ OUTPUT_LEVEL ] 57 | # 58 | # OUTPUT_LEVEL : INPUT_LIST OUTPUT_LIST , OUTPUT_LEVEL 59 | # | OUTPUT_LIST , OUTPUT_LEVEL 60 | # | INPUT_LIST OUTPUT_LIST 61 | # | OUTPUT_LIST 62 | # 63 | # INPUT_INSTRUCTION list of instruction dependent rules 64 | # 65 | # OUTPUT_INSTRUCTION list of instruction dependent rules 66 | # 67 | # !! OBS: OUTPUT_SEQ was added because Rockwell's ladder allows an output 68 | # instruction following another. Usually: 69 | # OUTPUT_LIST : OUTPUT_INSTRUCTION | OUTPUT_BRANCH 70 | # 71 | ################################################################################ 72 | import sys 73 | import logging 74 | from ply import yacc 75 | from runglex import tokens 76 | from runglex import runglex 77 | 78 | def rungyacc(debug=False): 79 | log = logging.getLogger('l5x2c') 80 | 81 | ################################################################################ 82 | # 83 | # RUNG : INPUT_LIST OUTPUT_LIST 84 | # | OUTPUT_LIST 85 | # 86 | ################################################################################ 87 | def p_rung_io(p): 88 | 'rung : input_list output_list SEMICOLON' 89 | p[0] = 'clear();push(true);' + p[1] + p[2] 90 | 91 | def p_rung_o(p): 92 | 'rung : output_list SEMICOLON' 93 | p[0] = 'clear();push(true);' + p[1] 94 | 95 | ################################################################################ 96 | # 97 | # INPUT_LIST : INPUT_INSTRUCTION 98 | # | INPUT_LIST INPUT_INSTRUCTION 99 | # | INPUT_BRANCH 100 | # | INPUT_LIST INPUT_BRANCH 101 | # 102 | ################################################################################ 103 | def p_input_list_i(p): 104 | 'input_list : input_instruction' 105 | p[0] = p[1] 106 | 107 | def p_input_list_ii(p): 108 | 'input_list : input_list input_instruction' 109 | p[0] = p[1] + p[2] 110 | 111 | def p_input_list_b(p): 112 | 'input_list : input_branch' 113 | p[0] = p[1] 114 | 115 | def p_input_list_ib(p): 116 | 'input_list : input_list input_branch' 117 | p[0] = p[1] + p[2] 118 | 119 | ################################################################################ 120 | # 121 | # INPUT_BRANCH : [ INPUT_LEVEL ] 122 | # | [] 123 | # 124 | ################################################################################ 125 | def p_input_branch_l(p): 126 | 'input_branch : LBRA input_level RBRA' 127 | p[0] = 'push(false);push(true);' + p[2] + 'or();and();' 128 | 129 | def p_input_branch_e(p): 130 | 'input_branch : LBRA RBRA' 131 | pass 132 | 133 | ################################################################################ 134 | # 135 | # INPUT_LEVEL : INPUT_LIST , INPUT_LEVEL 136 | # | INPUT_LIST 137 | # | , 138 | # | , INPUT_LEVEL 139 | # 140 | ################################################################################ 141 | def p_input_level_il(p): 142 | 'input_level : input_list COMMA input_level' 143 | p[0] = p[1] + 'or();push(true);' + p[3] 144 | 145 | def p_input_level_i(p): 146 | 'input_level : input_list' 147 | p[0] = p[1] 148 | 149 | def p_input_level_c(p): 150 | 'input_level : COMMA' 151 | p[0] = 'or();push(true);' 152 | 153 | 154 | def p_input_level_l(p): 155 | 'input_level : COMMA input_level' 156 | p[0] = 'or();push(true);' + p[2] 157 | 158 | ################################################################################ 159 | # 160 | # OUTPUT_LIST : OUTPUT_INSTRUCTION 161 | # | OUTPUT_BRANCH 162 | # 163 | ################################################################################ 164 | def p_output_list_i(p): 165 | 'output_list : output_seq' 166 | p[0] = p[1] 167 | 168 | def p_output_list_b(p): 169 | 'output_list : output_branch' 170 | p[0] = p[1] 171 | 172 | ################################################################################ 173 | # 174 | # OUTPUT_SEQ : OUTPUT_INSTRUCTION 175 | # | OUTPUT_SEQ OUTPUT_INSTRUCTION 176 | # 177 | ################################################################################ 178 | def p_output_seq_i(p): 179 | 'output_seq : output_instruction' 180 | p[0] = p[1] 181 | 182 | def p_output_seq_b(p): 183 | 'output_seq : output_seq output_instruction' 184 | p[0] = p[1] + p[2] 185 | 186 | 187 | ################################################################################ 188 | # 189 | # OUTPUT_BRANCH : [ OUTPUT_LEVEL ] 190 | # 191 | ################################################################################ 192 | def p_output_branch_l(p): 193 | 'output_branch : LBRA output_level RBRA' 194 | p[0] = 'push(acc());' + p[2] + 'pop();' 195 | 196 | ################################################################################ 197 | # 198 | # OUTPUT_LEVEL : INPUT_LIST OUTPUT_LIST , OUTPUT_LEVEL 199 | # | OUTPUT_LIST , OUTPUT_LEVEL 200 | # | INPUT_LIST OUTPUT_LIST 201 | # | OUTPUT_LIST 202 | # 203 | ################################################################################ 204 | def p_output_level_iol(p): 205 | 'output_level : input_list output_list COMMA output_level' 206 | p[0] = p[1] + p[2] + 'pop();push(acc());' + p[4] 207 | 208 | def p_output_level_ol(p): 209 | 'output_level : output_list COMMA output_level' 210 | p[0] = p[1] + 'pop();push(acc());' + p[3] 211 | 212 | def p_output_level_io(p): 213 | 'output_level : input_list output_list' 214 | p[0] = p[1] + p[2] 215 | 216 | def p_output_level_o(p): 217 | 'output_level : output_list' 218 | p[0] = p[1] 219 | 220 | ################################################################################ 221 | # 222 | # INPUT INSTRUCTIONS 223 | # 224 | ################################################################################ 225 | def p_input_instruction_xic(p): 226 | 'input_instruction : XIC LPAR parameter RPAR' 227 | p[0] = 'push(' + p[3] + ');and();' 228 | 229 | def p_input_instruction_xio(p): 230 | 'input_instruction : XIO LPAR parameter RPAR' 231 | p[0] = 'push(!' + p[3] + ');and();' 232 | 233 | def p_input_instruction_ons(p): 234 | 'input_instruction : ONS LPAR parameter RPAR' 235 | p[0] = 'if(' + p[3] + '==acc()){if(acc()){pop();push(false);}}else{' + p[3] + '=acc();}' 236 | 237 | def p_input_instruction_equ(p): 238 | 'input_instruction : EQU LPAR parameter COMMA parameter RPAR' 239 | p[0] = 'push(%s==%s);and();' % (p[3],p[5]) 240 | 241 | def p_input_instruction_geq(p): 242 | 'input_instruction : GEQ LPAR parameter COMMA parameter RPAR' 243 | p[0] = 'push(%s>=%s);and();' % (p[3],p[5]) 244 | 245 | def p_input_instruction_neq(p): 246 | 'input_instruction : NEQ LPAR parameter COMMA parameter RPAR' 247 | p[0] = 'push(%s!=%s);and();' % (p[3],p[5]) 248 | 249 | def p_input_instruction_leq(p): 250 | 'input_instruction : LEQ LPAR parameter COMMA parameter RPAR' 251 | p[0] = 'push(%s<%s);and();' % (p[3],p[5]) 252 | 253 | def p_input_instruction_grt(p): 254 | 'input_instruction : GRT LPAR parameter COMMA parameter RPAR' 255 | p[0] = 'push(%s>%s);and();' % (p[3],p[5]) 256 | 257 | def p_input_instruction_lim(p): 258 | 'input_instruction : LIM LPAR parameter COMMA parameter COMMA parameter RPAR' 259 | p[0] = 'if(acc()){{if({low}<={high}){{if({low}>={value}||{value}>={high}){{pop();push(false);}}}}else{{if({low}<={value}||{value}<={high}){{pop();push(false);}}}}}}'.format(low=p[3], value=p[5], high=p[7]) 260 | 261 | 262 | ################################################################################ 263 | # 264 | # OUTPUT INSTRUCTIONS 265 | # 266 | ################################################################################ 267 | def p_output_instruction_ote(p): 268 | 'output_instruction : OTE LPAR parameter RPAR' 269 | p[0] = p[3] + '=acc();' 270 | 271 | def p_output_instruction_otu(p): 272 | 'output_instruction : OTU LPAR parameter RPAR' 273 | p[0] = 'if(acc())' + p[3] + '=0;' 274 | 275 | def p_output_instruction_otl(p): 276 | 'output_instruction : OTL LPAR parameter RPAR' 277 | p[0] = 'if(acc())' + p[3] + '=1;' 278 | 279 | def p_output_instruction_res(p): 280 | 'output_instruction : RES LPAR parameter RPAR' 281 | p[0] = 'if(acc())' + p[3] + '.ACC=0;' 282 | 283 | def p_output_instruction_mov(p): 284 | 'output_instruction : MOV LPAR parameter COMMA parameter RPAR' 285 | p[0] = 'if(acc())' + p[5] + '=' + p[3] + ';' 286 | 287 | def p_output_instruction_cop(p): 288 | 'output_instruction : COP LPAR parameter COMMA parameter COMMA parameter RPAR' 289 | log.warning("Instruction COP is not supported. Instruction was ignored.") 290 | p[0] = '' 291 | 292 | def p_output_instruction_ton(p): 293 | 'output_instruction : TON LPAR parameter COMMA UNDEF_VAL COMMA UNDEF_VAL RPAR' 294 | p[0] = 'ton(acc(), &' + p[3] + ');' 295 | 296 | def p_output_instruction_tof(p): 297 | 'output_instruction : TOF LPAR parameter COMMA UNDEF_VAL COMMA UNDEF_VAL RPAR' 298 | p[0] = 'tof(acc(), &' + p[3] + ');' 299 | 300 | def p_output_instruction_ctu(p): 301 | 'output_instruction : CTU LPAR parameter COMMA UNDEF_VAL COMMA UNDEF_VAL RPAR' 302 | p[0] = 'ctu(acc(), &' + p[3] + ');' 303 | 304 | def p_output_instruction_jsr(p): 305 | 'output_instruction : JSR LPAR parameter COMMA NUMBER RPAR' 306 | p[0] = 'if(acc())%s();' % (p[3]) 307 | 308 | def p_output_instruction_btd(p): 309 | 'output_instruction : BTD LPAR parameter COMMA NUMBER COMMA parameter COMMA NUMBER COMMA NUMBER RPAR' 310 | log.warning("Instruction BTD is not supported. Instruction was ignored.") 311 | p[0] = '' 312 | 313 | def p_output_instruction_add(p): 314 | 'output_instruction : ADD LPAR parameter COMMA parameter COMMA parameter RPAR' 315 | p[0] = 'if(acc()){%s=%s+%s;};' % (p[7],p[3],p[5]) 316 | 317 | def p_output_instruction_sub(p): 318 | 'output_instruction : SUB LPAR parameter COMMA parameter COMMA parameter RPAR' 319 | p[0] = 'if(acc()){%s=%s-%s;};' % (p[7],p[3],p[5]) 320 | 321 | def p_output_instruction_clr(p): 322 | 'output_instruction : CLR LPAR parameter RPAR' 323 | p[0] = 'if(acc()){%s=0;};' % (p[3]) 324 | 325 | def p_output_instruction_div(p): 326 | 'output_instruction : DIV LPAR parameter COMMA parameter COMMA parameter RPAR' 327 | p[0] = 'if(acc()){%s=%s/%s;};' % (p[7],p[3],p[5]) 328 | 329 | def p_output_instruction_cpt(p): 330 | 'output_instruction : CPT LPAR parameter COMMA cpt_expression RPAR' 331 | p[0] = 'if(acc()){%s=%s;};' % (p[3],p[5]) 332 | 333 | def p_output_instruction_msg(p): 334 | 'output_instruction : MSG LPAR parameter RPAR' 335 | log.warning("Instruction MSG is not supported. Instruction was ignored.") 336 | p[0] = '' 337 | 338 | ################################################################################ 339 | # 340 | # PARAMETERS 341 | # 342 | ################################################################################ 343 | def p_parameter_tag(p): 344 | 'parameter : TAG' 345 | p[0] = p[1] 346 | 347 | def p_parameter_comm_tag(p): 348 | 'parameter : COMM_TAG' 349 | p[0] = p[1] 350 | 351 | def p_parameter_number(p): 352 | 'parameter : NUMBER' 353 | p[0] = p[1] 354 | 355 | def p_parameter_neg_number(p): 356 | 'parameter : CPT_MINUS NUMBER' 357 | p[0] = p[1] 358 | 359 | ################################################################################ 360 | # 361 | # CPT EXPRESSION 362 | # 363 | # expression : expression PLUS expression 364 | # | expression MINUS expression 365 | # | expression TIMES expression 366 | # | expression DIVIDE expression 367 | # | LPAREN expression RPAREN 368 | # | NUMBER 369 | # | parameter 370 | # 371 | ################################################################################ 372 | precedence = ( 373 | ('left', 'CPT_PLUS', 'CPT_MINUS'), 374 | ('left', 'CPT_TIMES', 'CPT_DIV'), 375 | ) 376 | def p_cpt_expression_plus(p): 377 | 'cpt_expression : cpt_expression CPT_PLUS cpt_expression' 378 | p[0] = '%s+%s' % (p[1],p[3]) 379 | 380 | def p_cpt_expression_minus(p): 381 | 'cpt_expression : cpt_expression CPT_MINUS cpt_expression' 382 | p[0] = '%s-%s' % (p[1],p[3]) 383 | 384 | def p_cpt_expression_times(p): 385 | 'cpt_expression : cpt_expression CPT_TIMES cpt_expression' 386 | p[0] = '%s*%s' % (p[1],p[3]) 387 | 388 | def p_cpt_expression_div(p): 389 | 'cpt_expression : cpt_expression CPT_DIV cpt_expression' 390 | p[0] = '%s/%s' % (p[1],p[3]) 391 | 392 | def p_cpt_expression_par(p): 393 | 'cpt_expression : LPAR cpt_expression RPAR' 394 | p[0] = '(%s)' % (p[2]) 395 | 396 | def p_cpt_expression_number(p): 397 | 'cpt_expression : NUMBER' 398 | p[0] = p[1] 399 | 400 | def p_cpt_expression_parameter(p): 401 | 'cpt_expression : parameter' 402 | p[0] = p[1] 403 | 404 | ################################################################################ 405 | # 406 | # ERROR RULE 407 | # 408 | ################################################################################ 409 | def p_error(p): 410 | log.error("Syntax error at '%s'" % repr(p)) 411 | raise SyntaxError 412 | 413 | return yacc.yacc(debug=debug,errorlog=log) 414 | 415 | 416 | 417 | 418 | #################################################### 419 | # 420 | # MAIN SCRIPT FOR COMMAND LINE EXECUTION 421 | # 422 | ################################################### 423 | def main(): 424 | # build the lexer 425 | lexer = runglex() 426 | # Build the parser 427 | parser = rungyacc() 428 | 429 | result = parser.parse(sys.stdin.readline()) 430 | print(result) 431 | 432 | if __name__== "__main__": 433 | main() 434 | -------------------------------------------------------------------------------- /testgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | ################################################################################ 4 | # Copyright (c) 2019 Alair Dias Junior 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # 25 | # This file is part of l5x2c. To know more about it, acccess: 26 | # https://github.com/alairjunior/l5x2c 27 | # 28 | ################################################################################ 29 | import os 30 | import logging 31 | import argparse 32 | from string import Template 33 | from runglex import tokens 34 | from runglex import runglex 35 | from rungyacc import rungyacc 36 | 37 | test_cases = [ 38 | { 39 | "rung" : "OTE(a);", 40 | "template" : 41 | ''' 42 | bool a; 43 | $rung 44 | assert(a); 45 | 46 | ''' 47 | }, 48 | { 49 | "rung" : "XIC(a)OTE(b);", 50 | "template" : 51 | ''' 52 | bool a,b; 53 | a = nondet_bool(); 54 | $rung 55 | assert(a == b); 56 | 57 | ''' 58 | }, 59 | { 60 | "rung" : "XIC(a)[XIC(b)XIO(c),XIO(b)XIC(c)][XIC(d)OTE(e),XIO(d)OTE(f)];", 61 | "template" : 62 | ''' 63 | bool a,b,c,d,e,f; 64 | a = nondet_bool(); 65 | b = nondet_bool(); 66 | c = nondet_bool(); 67 | d = nondet_bool(); 68 | $rung 69 | assert(e == (a && (b && !c || !b && c) && d)); 70 | assert(f == (a && (b && !c || !b && c) && !d)); 71 | 72 | ''' 73 | }, 74 | { 75 | "rung" : "XIC(a)[XIC(b)XIO(c),XIO(b)XIC(c)]XIC(d)OTE(e)OTE(f);", 76 | "template" : 77 | ''' 78 | bool a,b,c,d,e,f; 79 | a = nondet_bool(); 80 | b = nondet_bool(); 81 | c = nondet_bool(); 82 | d = nondet_bool(); 83 | $rung 84 | assert(e == (a && (b && !c || !b && c) && d)); 85 | assert(f == (a && (b && !c || !b && c) && d)); 86 | 87 | ''' 88 | }, 89 | { 90 | "rung" : "XIC(a)[XIC(b)XIO(c),XIO(b)XIC(c)][XIC(d)OTE(e),XIO(d)OTE(f)]OTE(g);", 91 | "template" : 92 | ''' 93 | // Rung: 94 | // XIC(a)[XIC(b)XIO(c),XIO(b)XIC(c)][XIC(d)OTE(e),XIO(d)OTE(f)]OTE(g); 95 | // is invalid and should not be generated. Rung parser is wrong! 96 | assert(false); 97 | ''' 98 | }, 99 | { 100 | "rung" : "XIC(a)MOV(b,c)MOV(b,d);", 101 | "template" : 102 | ''' 103 | bool a = nondet_bool(); 104 | int b = nondet_int(); 105 | int c = nondet_int(); 106 | int d = nondet_int(); 107 | int old_c = c; 108 | int old_d = d; 109 | $rung 110 | if (a) assert(b == c); 111 | if (a) assert(b == d); 112 | if (!a) assert (c == old_c); 113 | if (!a) assert (d == old_d); 114 | 115 | ''' 116 | }, 117 | { 118 | "rung" : "XIC(a)TON(t,?,?);", 119 | "template" : 120 | ''' 121 | bool a; 122 | bool reset = false; 123 | int count = 0; 124 | int iterations = 10; 125 | 126 | timer t = { 127 | .EN = nondet_bool(), 128 | .TT = nondet_bool(), 129 | .DN = nondet_bool(), 130 | .PRE = nondet_int(), 131 | .ACC = nondet_int() 132 | }; 133 | 134 | assume(t.ACC >= 0); 135 | assume(t.PRE < (iterations - 1) * get_scan_time()); 136 | assume(t.PRE > 0); 137 | 138 | for (int i = 0; i < iterations; ++i) { 139 | a = nondet_bool(); 140 | 141 | $rung 142 | 143 | if (!a) reset = true; 144 | 145 | if (a) ++count; 146 | else count=0; 147 | 148 | if (reset) { 149 | if (count > ceil((double)t.PRE / (double)get_scan_time())) 150 | assert(t.DN); 151 | else 152 | assert(!t.DN); 153 | } 154 | 155 | assert(a == t.EN); 156 | 157 | assert(t.TT == (t.EN && !t.DN)); 158 | } 159 | 160 | ''' 161 | }, 162 | { 163 | "rung" : "XIC(a)TOF(t,?,?);", 164 | "template" : 165 | ''' 166 | bool a; 167 | bool reset = false; 168 | int count = 0; 169 | int iterations = 10; 170 | 171 | timer t = { 172 | .EN = nondet_bool(), 173 | .TT = nondet_bool(), 174 | .DN = nondet_bool(), 175 | .PRE = nondet_int(), 176 | .ACC = nondet_int() 177 | }; 178 | 179 | assume(t.ACC >= 0); 180 | assume(t.PRE < (iterations - 1) * get_scan_time()); 181 | assume(t.PRE > 0); 182 | 183 | for (int i = 0; i < iterations; ++i) { 184 | a = nondet_bool(); 185 | 186 | $rung 187 | 188 | if (a) reset = true; 189 | 190 | if (!a) ++count; 191 | else count=0; 192 | 193 | if (reset) { 194 | if (count > ceil((double)t.PRE / (double)get_scan_time())) 195 | assert(!t.DN); 196 | else 197 | assert(t.DN); 198 | } 199 | 200 | assert(a == t.EN); 201 | 202 | assert(t.TT == (!t.EN && t.DN)); 203 | } 204 | 205 | ''' 206 | }, 207 | 208 | ] 209 | 210 | #################################################### 211 | # 212 | # ADD TEMPLATES TO THE GENERATED FILE 213 | # 214 | ################################################### 215 | def addTemplates(f, parameters): 216 | with open('plcmodel.template', 'r') as t: 217 | text = t.read() 218 | template = Template(text) 219 | f.write(template.substitute(parameters)) 220 | f.write('int nondet_int(){ int x; return x; }\n') 221 | f.write('bool nondet_bool(){ bool x; return x; }\n\n') 222 | f.write('void assume (bool e) { while (!e) ; }\n\n') 223 | 224 | #################################################### 225 | # 226 | # MAIN SCRIPT FOR COMMAND LINE EXECUTION 227 | # 228 | ################################################### 229 | def main(): 230 | description = "Generate tests for the runglex and rungyacc files" 231 | parser = argparse.ArgumentParser(description=description) 232 | 233 | parser.add_argument('-ss', '--stack_size', type=int, default=1000, 234 | help="Stack size for the stack machine") 235 | parser.add_argument('-st', '--scan_time', type=int, default=100, 236 | help="Scan time for the PLC model") 237 | 238 | args = vars(parser.parse_args()) 239 | 240 | parameters = { 241 | 'stack_size': args['stack_size'], 242 | 'scan_time': args['scan_time'] 243 | } 244 | 245 | # supress syntax error messages 246 | logger = logging.getLogger('l5x2c') 247 | logger.setLevel(logging.CRITICAL) 248 | 249 | # build the lexer 250 | lexer = runglex() 251 | # Build the parser 252 | rungparser = rungyacc() 253 | 254 | if not os.path.exists('tests'): 255 | os.makedirs('tests') 256 | 257 | with open('tests/tests.c', 'w') as f: 258 | addTemplates(f,parameters) 259 | tests = [] 260 | for i in range(0,len(test_cases)): 261 | try: 262 | rung = rungparser.parse(test_cases[i]['rung']) 263 | f.write('void test_%d() {\n' % (i+1)) 264 | template = Template(test_cases[i]['template']) 265 | f.write(template.substitute({'rung': rung})) 266 | f.write('}\n\n') 267 | tests.append(i) 268 | except SyntaxError: 269 | pass 270 | 271 | f.write('int main() {\n') 272 | for test in tests: 273 | f.write(' test_%d();\n' % (test+1)) 274 | f.write('}\n') 275 | 276 | if __name__== "__main__": 277 | main() 278 | --------------------------------------------------------------------------------