├── .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 |
--------------------------------------------------------------------------------