tallakt-plcutil 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/README.rdoc +2 -1
- data/bin/awlpp +3 -4
- data/bin/intouchpp +2 -2
- data/bin/pl7tointouch +2 -2
- data/bin/s7tointouch +2 -2
- data/lib/{tallakt-plcutil → plcutil}/schneider/pl7_to_intouch_runner.rb +3 -12
- data/lib/plcutil/siemens/awl/array_type.rb +21 -0
- data/lib/plcutil/siemens/awl/awl.treetop +172 -0
- data/lib/plcutil/siemens/awl/awl_file.rb +91 -0
- data/lib/plcutil/siemens/awl/basic_type.rb +52 -0
- data/lib/plcutil/siemens/awl/db_address.rb +52 -0
- data/lib/plcutil/siemens/awl/struct_type.rb +52 -0
- data/lib/plcutil/siemens/awl/symlist_file.rb +28 -0
- data/lib/plcutil/siemens/awl/treetop_nodes.rb +110 -0
- data/lib/plcutil/siemens/awl_pretty_print_runner.rb +48 -0
- data/lib/{tallakt-plcutil → plcutil}/siemens/sdffile.rb +0 -0
- data/lib/{tallakt-plcutil → plcutil}/siemens/step7_to_intouch_runner.rb +3 -3
- data/lib/{tallakt-plcutil → plcutil}/version.rb +1 -1
- data/lib/{tallakt-plcutil → plcutil}/wonderware/intouch_pretty_print_runner.rb +1 -1
- data/lib/{tallakt-plcutil → plcutil}/wonderware/intouchfile.rb +0 -0
- data/lib/{tallakt-plcutil → plcutil}/wonderware/standard_sections.yaml +0 -0
- data/lib/{tallakt-plcutil.rb → plcutil.rb} +1 -1
- metadata +76 -18
- data/Rakefile +0 -36
- data/lib/tallakt-plcutil/siemens/awl_pretty_print_runner.rb +0 -87
- data/lib/tallakt-plcutil/siemens/awlfile.rb +0 -295
data/History.txt
CHANGED
data/README.rdoc
CHANGED
@@ -8,7 +8,8 @@ A set of command line utilities and helper classes to convert between IO list fi
|
|
8
8
|
|
9
9
|
Currently Siemens, Schneider and Wonderware are supported, Schneider and Wonderware IAS support planned in the near future.
|
10
10
|
|
11
|
-
|
11
|
+
The utilities in this gem are somewhat rough quality, no guarantees are given
|
12
|
+
wrt correct operation. Even so, I hope they might be useful.
|
12
13
|
|
13
14
|
== FEATURES/PROBLEMS:
|
14
15
|
|
data/bin/awlpp
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require '
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
require 'plcutil/siemens/awl_pretty_print_runner'
|
4
4
|
|
5
|
-
|
6
|
-
PlcUtil::AwlPrettyPrintRunner.new ARGV
|
5
|
+
PlcUtil::AwlPrettyPrintRunner.run
|
7
6
|
|
data/bin/intouchpp
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require '
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
require 'plcutil/wonderware/intouch_pretty_print_runner'
|
4
4
|
|
5
5
|
# try: intouchpp --help
|
6
6
|
PlcUtil::IntouchPrettyPrintRunner.new ARGV
|
data/bin/pl7tointouch
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require '
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
require 'plcutil/schneider/pl7_to_intouch_runner'
|
4
4
|
|
5
5
|
# try: pl7tointouch --help
|
6
6
|
PlcUtil::PL7ToIntouchRunner.new ARGV
|
data/bin/s7tointouch
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require '
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
require 'plcutil/siemens/step7_to_intouch_runner'
|
4
4
|
|
5
5
|
# try: s7tointouch --help
|
6
6
|
PlcUtil::Step7ToIntouchRunner.new ARGV
|
@@ -2,7 +2,7 @@
|
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
4
|
require 'optparse'
|
5
|
-
require '
|
5
|
+
require 'plcutil/wonderware/intouchfile'
|
6
6
|
require 'ostruct'
|
7
7
|
|
8
8
|
module PlcUtil
|
@@ -46,15 +46,10 @@ module PlcUtil
|
|
46
46
|
|
47
47
|
def read_pl7_file(io)
|
48
48
|
io.each_line do |l|
|
49
|
-
mres = l.match(/^%M(W
|
49
|
+
mres = l.match(/^%M(W)?(\d+)(:X(\d+))?\t(\S+)\t(\S+)(\t\"?([^\t"]*))?/)
|
50
50
|
if mres
|
51
51
|
item = OpenStruct.new
|
52
|
-
|
53
|
-
when 'W'
|
54
|
-
item.is_word = true
|
55
|
-
when 'F'
|
56
|
-
item.is_float = true
|
57
|
-
end
|
52
|
+
item.is_word = mres[1]
|
58
53
|
item.index = mres[2].to_i
|
59
54
|
item.bit_index = mres[4] && mres[4].to_i
|
60
55
|
item.tagname = mres[5]
|
@@ -88,10 +83,6 @@ module PlcUtil
|
|
88
83
|
@intouchfile.new_io_int(tag, data) do |io|
|
89
84
|
io.item_name = '%d S' % (item.index + 400001)
|
90
85
|
end
|
91
|
-
elsif item.is_float
|
92
|
-
@intouchfile.new_io_real(tag, data) do |io|
|
93
|
-
io.item_name = '%d F' % (item.index + 400001)
|
94
|
-
end
|
95
86
|
else
|
96
87
|
@intouchfile.new_io_disc(tag, data) do |io|
|
97
88
|
io.item_name = (item.index + 1).to_s
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module PlcUtil
|
2
|
+
module Awl
|
3
|
+
class ArrayType
|
4
|
+
attr_accessor :range, :element_type
|
5
|
+
|
6
|
+
def initialize(element_type, range)
|
7
|
+
@range, @element_type = range, element_type
|
8
|
+
end
|
9
|
+
|
10
|
+
def skip_padding(addr)
|
11
|
+
addr.next_word
|
12
|
+
end
|
13
|
+
|
14
|
+
def end_address(start_addr)
|
15
|
+
start_addr.skip type.bit_size * range.count
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module PlcUtil
|
2
|
+
module Awl
|
3
|
+
grammar AwlGrammar
|
4
|
+
rule root
|
5
|
+
toplevel <TopLevelNode>
|
6
|
+
end
|
7
|
+
|
8
|
+
rule toplevel
|
9
|
+
(udt <UdtNode> / db <DbNode> / ob <ObNode>)*
|
10
|
+
end
|
11
|
+
|
12
|
+
rule db
|
13
|
+
'DATA_BLOCK ' name eol
|
14
|
+
title
|
15
|
+
'VERSION : ' version eol
|
16
|
+
eol*
|
17
|
+
root_struct_decl
|
18
|
+
'BEGIN' eol
|
19
|
+
assignment*
|
20
|
+
'END_DATA_BLOCK' eol
|
21
|
+
ws_eol
|
22
|
+
end
|
23
|
+
|
24
|
+
rule udt
|
25
|
+
'TYPE ' name eol
|
26
|
+
'VERSION : ' version eol
|
27
|
+
eol*
|
28
|
+
root_struct_decl
|
29
|
+
'END_TYPE' eol
|
30
|
+
ws_eol
|
31
|
+
end
|
32
|
+
|
33
|
+
rule ob
|
34
|
+
'ORGANIZATION_BLOCK ' name eol
|
35
|
+
title
|
36
|
+
'VERSION : ' version eol
|
37
|
+
eol*
|
38
|
+
(!ob_end .)*
|
39
|
+
ob_end eol
|
40
|
+
ws_eol
|
41
|
+
end
|
42
|
+
|
43
|
+
rule name
|
44
|
+
quoted_string <QuotedNameNode> / db_name <DbNameNode>
|
45
|
+
end
|
46
|
+
|
47
|
+
rule quoted_string
|
48
|
+
'"' v:(!'"' .)* '"'
|
49
|
+
end
|
50
|
+
|
51
|
+
rule single_quoted_string
|
52
|
+
single_quote (!single_quote .)* single_quote
|
53
|
+
end
|
54
|
+
|
55
|
+
rule single_quote
|
56
|
+
"'"
|
57
|
+
end
|
58
|
+
|
59
|
+
rule db_name
|
60
|
+
'DB ' number:([0-9]+)
|
61
|
+
end
|
62
|
+
|
63
|
+
rule version
|
64
|
+
[0-9]+ '.' [0-9]+
|
65
|
+
end
|
66
|
+
|
67
|
+
rule title
|
68
|
+
'TITLE =' optional_title:(ws title_quoted:(quoted_string))? eol
|
69
|
+
end
|
70
|
+
|
71
|
+
rule ob_end
|
72
|
+
'END_ORGANIZATION_BLOCK'
|
73
|
+
end
|
74
|
+
|
75
|
+
rule assignment
|
76
|
+
ws identifier ws ':=' ws value ';' ws eol
|
77
|
+
end
|
78
|
+
|
79
|
+
rule declaration
|
80
|
+
ws
|
81
|
+
identifier
|
82
|
+
' : '
|
83
|
+
d:(array_declaration <ArrayDeclarationNode> / nonarray_declaration <NonArrayDeclarationNode>)
|
84
|
+
eol
|
85
|
+
end
|
86
|
+
|
87
|
+
rule nonarray_declaration
|
88
|
+
non_array_data_type
|
89
|
+
ws?
|
90
|
+
initial_value? ws?
|
91
|
+
';'
|
92
|
+
ws? comment:(line_comment?)
|
93
|
+
end
|
94
|
+
|
95
|
+
rule initial_value
|
96
|
+
':=' ws single_quoted_string
|
97
|
+
end
|
98
|
+
|
99
|
+
rule array_declaration
|
100
|
+
'ARRAY [' ar_begin:([0-9]+) ' .. ' ar_end:([0-9]+) ' ] OF '
|
101
|
+
array_comment:(line_comment eol ws)? non_array_data_type ws ';' ws
|
102
|
+
end
|
103
|
+
|
104
|
+
rule non_array_data_type
|
105
|
+
basic_data_type <BasicDataTypeNode>
|
106
|
+
/
|
107
|
+
struct_data_type <StructDataTypeNode>
|
108
|
+
/
|
109
|
+
udt_data_type <UdtDataTypeNode>
|
110
|
+
end
|
111
|
+
|
112
|
+
rule struct_data_type
|
113
|
+
'STRUCT' ws? eol
|
114
|
+
decl:declaration*
|
115
|
+
ws 'END_STRUCT'
|
116
|
+
end
|
117
|
+
|
118
|
+
rule root_struct_decl
|
119
|
+
ws struct_data_type ws ';' ws? eol
|
120
|
+
end
|
121
|
+
|
122
|
+
rule udt_data_type
|
123
|
+
quoted_string
|
124
|
+
end
|
125
|
+
|
126
|
+
rule identifier
|
127
|
+
non_ws+
|
128
|
+
end
|
129
|
+
|
130
|
+
rule line_comment
|
131
|
+
'//' comment_body:(!eol .)*
|
132
|
+
end
|
133
|
+
|
134
|
+
rule basic_data_type
|
135
|
+
(
|
136
|
+
'INT' /
|
137
|
+
'DINT' /
|
138
|
+
'BOOL' /
|
139
|
+
'BYTE' /
|
140
|
+
'WORD' /
|
141
|
+
'DWORD' /
|
142
|
+
'TIME_OF_DAY' /
|
143
|
+
'REAL' /
|
144
|
+
'TIME' /
|
145
|
+
'S5TIME' /
|
146
|
+
'DATE' /
|
147
|
+
'CHAR'
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
rule value
|
152
|
+
(single_quoted_string / (!(ws / ';') .)+)
|
153
|
+
end
|
154
|
+
|
155
|
+
rule eol
|
156
|
+
"\r"? "\n"
|
157
|
+
end
|
158
|
+
|
159
|
+
rule ws
|
160
|
+
(' ' / "\t")+
|
161
|
+
end
|
162
|
+
|
163
|
+
rule non_ws
|
164
|
+
(!ws .)
|
165
|
+
end
|
166
|
+
|
167
|
+
rule ws_eol
|
168
|
+
(ws / eol)+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'polyglot'
|
4
|
+
require 'treetop'
|
5
|
+
require 'plcutil/siemens/awl/basic_type'
|
6
|
+
require 'plcutil/siemens/awl/struct_type'
|
7
|
+
require 'plcutil/siemens/awl/array_type'
|
8
|
+
require 'plcutil/siemens/awl/treetop_nodes'
|
9
|
+
require 'plcutil/siemens/awl/db_address'
|
10
|
+
require 'plcutil/siemens/awl/awl.treetop'
|
11
|
+
|
12
|
+
|
13
|
+
module PlcUtil
|
14
|
+
module Awl
|
15
|
+
class AwlFile
|
16
|
+
attr_reader :symlist
|
17
|
+
|
18
|
+
def initialize(filename, options = {})
|
19
|
+
@symlist = options[:symlist] && SymlistFile.new(options[:symlist])
|
20
|
+
# parse file
|
21
|
+
parser = PlcUtil::Awl::AwlGrammarParser.new
|
22
|
+
awl_nodes = parser.parse File.read(filename)
|
23
|
+
@awl = awl_nodes && awl_nodes.visit
|
24
|
+
if !@awl
|
25
|
+
raise [
|
26
|
+
"Unable to parse file: #{filename}",
|
27
|
+
"Failure on line #{parser.failure_line} column #{parser.failure.column}",
|
28
|
+
"Details:",
|
29
|
+
parser.failure_reason.inspect,
|
30
|
+
].join("\n")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def lookup_symlist(tag)
|
35
|
+
(options[:blocks] && options[:blocks][tag]) || (@symlist && @symlist[tag])
|
36
|
+
end
|
37
|
+
|
38
|
+
def each_exploded(options = {}, &block)
|
39
|
+
@awl[:dbs].each do |raw|
|
40
|
+
db = create_struct raw
|
41
|
+
name = if options[:no_block]
|
42
|
+
''
|
43
|
+
else
|
44
|
+
raw[:id]
|
45
|
+
end
|
46
|
+
|
47
|
+
a = DbAddress.new 0, db_address(raw[:name])
|
48
|
+
db.each_exploded(a, name) do |addr, name, comment, type_name|
|
49
|
+
yield addr, name, comment, type_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def db_address(name)
|
55
|
+
n = @symlist[name]
|
56
|
+
n && n.gsub(/\s/, '')
|
57
|
+
end
|
58
|
+
private :db_address
|
59
|
+
|
60
|
+
def get_udt(name)
|
61
|
+
udt = @awl[:udts].find {|u| u[:name] == name }
|
62
|
+
raise "Could not find UDT with name: #{name}" unless udt
|
63
|
+
create_struct udt
|
64
|
+
end
|
65
|
+
private :get_udt
|
66
|
+
|
67
|
+
def create_struct(raw)
|
68
|
+
StructType.new.tap do |s|
|
69
|
+
raw[:entries].each do |e|
|
70
|
+
base_type = case e[:data_type]
|
71
|
+
when Hash
|
72
|
+
# anonymous structure inline
|
73
|
+
create_struct(e[:data_type])
|
74
|
+
when String
|
75
|
+
# UDT
|
76
|
+
get_udt e[:data_type]
|
77
|
+
when Symbol
|
78
|
+
BasicType::create e[:data_type]
|
79
|
+
end
|
80
|
+
if e.key? :array
|
81
|
+
s.add e[:id], ArrayType.new(base_type, e[:array]), e[:comment]
|
82
|
+
else
|
83
|
+
s.add e[:id], base_type, e[:comment]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
private :create_struct
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PlcUtil
|
2
|
+
module Awl
|
3
|
+
class BasicType
|
4
|
+
TYPES = {
|
5
|
+
:bool => 1,
|
6
|
+
:byte => 8,
|
7
|
+
:char => 8,
|
8
|
+
:date => 16,
|
9
|
+
:dint => 32,
|
10
|
+
:dword => 32,
|
11
|
+
:int => 16,
|
12
|
+
:real => 32,
|
13
|
+
:s5time => 16,
|
14
|
+
:time => 32,
|
15
|
+
:time_of_day => 32,
|
16
|
+
:word => 16,
|
17
|
+
:date_and_time => 32, # only in VAR_TEMP
|
18
|
+
:timer => 1, # only in FC calls
|
19
|
+
:cont_c => 125 * 8
|
20
|
+
}
|
21
|
+
|
22
|
+
attr_accessor :bit_size, :type_name
|
23
|
+
|
24
|
+
def initialize(bit_size, type_name)
|
25
|
+
@bit_size, @type_name = bit_size, type_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def skip_padding(addr)
|
29
|
+
case bit_size
|
30
|
+
when 1
|
31
|
+
addr
|
32
|
+
when 8
|
33
|
+
addr.next_byte
|
34
|
+
else
|
35
|
+
addr.next_word
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def end_address(start_addr)
|
40
|
+
start_addr.skip bit_size
|
41
|
+
end
|
42
|
+
|
43
|
+
def BasicType.create(type_key)
|
44
|
+
b = TYPES[type_key]
|
45
|
+
raise "Could not find type: #{type_key.to_s}" unless b
|
46
|
+
BasicType.new b, type_key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PlcUtil
|
2
|
+
module Awl
|
3
|
+
class DbAddress
|
4
|
+
attr_accessor :data_block_addr
|
5
|
+
|
6
|
+
def initialize(bit_addr, data_block_addr = nil)
|
7
|
+
@bit_addr, @data_block_addr = bit_addr, data_block_addr
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
(data_block_addr || 'DB???') + ',' + byte.to_s + '.' + bit.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def bit
|
15
|
+
@bit_addr % 8
|
16
|
+
end
|
17
|
+
|
18
|
+
def byte
|
19
|
+
@bit_addr / 8
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_word
|
23
|
+
# simatic packing rules
|
24
|
+
# 1. 16 bit values aligned at modulo 2 address 0.0, 2.0, 4.0, 8.0, ...
|
25
|
+
# 2. 8 bit values aligned at each address, 0.0, 1.0, 2.0, ...
|
26
|
+
# 3. Structs are aligned modulo 2
|
27
|
+
# 4. Bools are aligned like 8 bit values, consecutive bools occupy a single byte
|
28
|
+
|
29
|
+
DbAddress.new(next_n(16), data_block_addr)
|
30
|
+
end
|
31
|
+
|
32
|
+
def next_byte
|
33
|
+
DbAddress.new(next_n(8), data_block_addr)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def next_n(size)
|
38
|
+
m = @bit_addr % size
|
39
|
+
if m > 0
|
40
|
+
(@bit_addr / size + 1) * size
|
41
|
+
else
|
42
|
+
@bit_addr
|
43
|
+
end
|
44
|
+
end
|
45
|
+
private :next_n
|
46
|
+
|
47
|
+
def skip(bit_count)
|
48
|
+
DbAddress.new @bit_addr + bit_count, data_block_addr
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PlcUtil
|
2
|
+
module Awl
|
3
|
+
class StructType
|
4
|
+
def initialize
|
5
|
+
@children = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(name, type, comment)
|
9
|
+
@children << {:name => name, :type => type, :comment => comment}
|
10
|
+
end
|
11
|
+
|
12
|
+
def skip_padding(addr)
|
13
|
+
addr.next_word
|
14
|
+
end
|
15
|
+
|
16
|
+
def end_address(start_addr)
|
17
|
+
@children.inject(start_addr) {|addr, ch| ch[:type].end_address addr }
|
18
|
+
end
|
19
|
+
|
20
|
+
def each_exploded(start_addr, name_prefix, &block)
|
21
|
+
addr = skip_padding start_addr
|
22
|
+
@children.each do |ch|
|
23
|
+
full_name = if !name_prefix || name_prefix.empty?
|
24
|
+
ch[:name]
|
25
|
+
else
|
26
|
+
"#{name_prefix}.#{ch[:name]}"
|
27
|
+
end
|
28
|
+
case ch[:type]
|
29
|
+
when StructType
|
30
|
+
addr = ch[:type].each_exploded(addr, full_name) do |a, n, c, t|
|
31
|
+
yield a, n, c, t
|
32
|
+
end
|
33
|
+
when ArrayType
|
34
|
+
ct = ch[:type]
|
35
|
+
addr = ct.skip_padding addr
|
36
|
+
ct.range.each do |n|
|
37
|
+
yield addr, "#{full_name}[#{n}]", ch[:comment], ct.element_type.type_name
|
38
|
+
addr = ct.element_type.end_address addr
|
39
|
+
end
|
40
|
+
when BasicType
|
41
|
+
ct = ch[:type]
|
42
|
+
addr = ct.skip_padding addr
|
43
|
+
yield addr, full_name, ch[:comment], ct.type_name
|
44
|
+
addr = ct.end_address addr
|
45
|
+
end
|
46
|
+
end
|
47
|
+
addr
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'dbf'
|
2
|
+
|
3
|
+
module PlcUtil
|
4
|
+
module Awl
|
5
|
+
class SymlistFile
|
6
|
+
def initialize(filename)
|
7
|
+
@symlist = {}
|
8
|
+
raise 'Specified symlist file not found' unless File.exists? filename
|
9
|
+
table = DBF::Table.new filename
|
10
|
+
table.each do |rec|
|
11
|
+
next unless rec
|
12
|
+
@symlist[rec.attributes['_skz']] = rec.attributes['_opiec']
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](tag)
|
17
|
+
lookup tag
|
18
|
+
end
|
19
|
+
|
20
|
+
def lookup(tag)
|
21
|
+
@symlist[tag]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
|
3
|
+
module PlcUtil
|
4
|
+
module Awl
|
5
|
+
module AwlGrammar
|
6
|
+
module StructHelpers
|
7
|
+
def struct_visit_helper(struct_node)
|
8
|
+
struct_node.decl.elements.map do |el|
|
9
|
+
{ :id => el.identifier.text_value }.tap do |result|
|
10
|
+
el.d.visit result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module TopLevelNode
|
17
|
+
def visit
|
18
|
+
{
|
19
|
+
:dbs => [],
|
20
|
+
:udts => [],
|
21
|
+
:obs => []
|
22
|
+
}.tap do |result|
|
23
|
+
elements.each {|e| e.visit result }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module DbNode
|
29
|
+
include StructHelpers
|
30
|
+
def visit(root)
|
31
|
+
root[:dbs] << {}.tap do |db|
|
32
|
+
name.hash_entry db
|
33
|
+
db[:entries] = struct_visit_helper root_struct_decl.struct_data_type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module UdtNode
|
39
|
+
include StructHelpers
|
40
|
+
def visit(root)
|
41
|
+
root[:udts] << {}.tap do |udt|
|
42
|
+
name.hash_entry udt
|
43
|
+
udt[:entries] = struct_visit_helper root_struct_decl.struct_data_type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ObNode
|
49
|
+
def visit(root)
|
50
|
+
root[:obs] << {}.tap do |ob|
|
51
|
+
ob[:title] = title.optional_title.title_quoted.v.text_value
|
52
|
+
name.hash_entry ob
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
module ArrayDeclarationNode
|
59
|
+
def visit(declaration)
|
60
|
+
declaration[:array] = (ar_begin.text_value.to_i)..(ar_end.text_value.to_i)
|
61
|
+
declaration[:comment] = array_comment.line_comment.comment_body.text_value unless array_comment.terminal?
|
62
|
+
non_array_data_type.visit declaration
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module NonArrayDeclarationNode
|
67
|
+
def visit(declaration)
|
68
|
+
declaration[:comment] = comment.comment_body.text_value unless comment.terminal?
|
69
|
+
non_array_data_type.visit declaration
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module BasicDataTypeNode
|
74
|
+
def visit(declaration)
|
75
|
+
declaration[:data_type] = text_value.downcase.to_sym
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module StructDataTypeNode
|
80
|
+
include StructHelpers
|
81
|
+
def visit(declaration)
|
82
|
+
declaration[:data_type] = {}.tap do |struct|
|
83
|
+
struct[:entries] = struct_visit_helper self
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module UdtDataTypeNode
|
89
|
+
def visit(declaration)
|
90
|
+
declaration[:data_type] = v.text_value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
module QuotedNameNode
|
98
|
+
def hash_entry(h)
|
99
|
+
h[:name] = v.text_value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module DbNameNode
|
104
|
+
def hash_entry(h)
|
105
|
+
h[:db_number] = number.to_s.to_i
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'clamp'
|
4
|
+
require 'plcutil/siemens/awl/awl_file'
|
5
|
+
require 'plcutil/siemens/awl/symlist_file'
|
6
|
+
|
7
|
+
module PlcUtil
|
8
|
+
# Command line tool to read and output an awl file
|
9
|
+
class AwlPrettyPrintRunner < Clamp::Command
|
10
|
+
def execute
|
11
|
+
opt = {
|
12
|
+
:symlist => symlist,
|
13
|
+
:blocks => Hash[block_name.split(/[,=]/)]
|
14
|
+
}
|
15
|
+
|
16
|
+
file_list.each do |filename|
|
17
|
+
process_awl_file Awl::AwlFile.new(filename, opt)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fix_name(name)
|
22
|
+
if no_block?
|
23
|
+
name.sub /^[^\.]*\./, ''
|
24
|
+
else
|
25
|
+
name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_awl_file(awl)
|
30
|
+
awl.each_exploded do |addr, name, comment, type_name|
|
31
|
+
puts '%-11s %-40s%-10s%s' % [
|
32
|
+
addr.to_s,
|
33
|
+
fix_name(name),
|
34
|
+
type_name.to_s.upcase,
|
35
|
+
comment
|
36
|
+
]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
option %w(--no-block -n), :flag, "don't use datablock as part of the tagname"
|
41
|
+
option %w(--symlist -s), 'FILE', 'specify SYMLIST.DBF frfom Step 7 project'
|
42
|
+
option %w(--block-name -b), 'NAME=ADDR,NAME=ADDR', 'specify DB adress directly instead of using SYMLIST.DBF', :default => '' do |s|
|
43
|
+
raise 'Invalid format of block names, example use: A=DB20,B=DB21' unless s.match(/(\w+=\w+,)*(\w+=\w+)/)
|
44
|
+
end
|
45
|
+
|
46
|
+
parameter "FILE ...", 'awl files to read (exported inside Step 7)'
|
47
|
+
end
|
48
|
+
end
|
File without changes
|
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require '
|
4
|
+
require 'plcutil/siemens/awl/awl_file'
|
5
|
+
require 'plcutil/siemens/sdffile'
|
6
|
+
require 'plcutil/wonderware/intouchfile'
|
7
7
|
|
8
8
|
module PlcUtil
|
9
9
|
# Command line tool to read and output an awl file
|
File without changes
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tallakt-plcutil
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,37 +13,96 @@ date: 2010-10-12 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dbf
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.6
|
21
|
+
version: '1.6'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.6'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: polyglot
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.3'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: treetop
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.4'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: clamp
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.4'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0.4'
|
25
78
|
description: Ruby library for using Siemens, Schneider and Intouch files
|
26
79
|
email: tallak@tveide.net
|
27
80
|
executables:
|
28
81
|
- awlpp
|
29
82
|
- intouchpp
|
30
83
|
- pl7tointouch
|
31
|
-
- s7tointouch
|
32
84
|
extensions: []
|
33
85
|
extra_rdoc_files:
|
34
86
|
- LICENSE
|
35
87
|
- README.rdoc
|
36
88
|
files:
|
37
|
-
- lib/
|
38
|
-
- lib/
|
39
|
-
- lib/
|
40
|
-
- lib/
|
41
|
-
- lib/
|
42
|
-
- lib/
|
43
|
-
- lib/
|
44
|
-
- lib/
|
45
|
-
- lib/
|
46
|
-
- lib/
|
89
|
+
- lib/plcutil/schneider/pl7_to_intouch_runner.rb
|
90
|
+
- lib/plcutil/siemens/awl/array_type.rb
|
91
|
+
- lib/plcutil/siemens/awl/awl.treetop
|
92
|
+
- lib/plcutil/siemens/awl/awl_file.rb
|
93
|
+
- lib/plcutil/siemens/awl/basic_type.rb
|
94
|
+
- lib/plcutil/siemens/awl/db_address.rb
|
95
|
+
- lib/plcutil/siemens/awl/struct_type.rb
|
96
|
+
- lib/plcutil/siemens/awl/symlist_file.rb
|
97
|
+
- lib/plcutil/siemens/awl/treetop_nodes.rb
|
98
|
+
- lib/plcutil/siemens/awl_pretty_print_runner.rb
|
99
|
+
- lib/plcutil/siemens/sdffile.rb
|
100
|
+
- lib/plcutil/siemens/step7_to_intouch_runner.rb
|
101
|
+
- lib/plcutil/version.rb
|
102
|
+
- lib/plcutil/wonderware/intouchfile.rb
|
103
|
+
- lib/plcutil/wonderware/intouch_pretty_print_runner.rb
|
104
|
+
- lib/plcutil/wonderware/standard_sections.yaml
|
105
|
+
- lib/plcutil.rb
|
47
106
|
- bin/awlpp
|
48
107
|
- bin/intouchpp
|
49
108
|
- bin/pl7tointouch
|
@@ -54,7 +113,6 @@ files:
|
|
54
113
|
- History.txt
|
55
114
|
- LICENSE
|
56
115
|
- README.rdoc
|
57
|
-
- Rakefile
|
58
116
|
homepage: http://github.com/tallakt/plcutil
|
59
117
|
licenses: []
|
60
118
|
post_install_message:
|
@@ -76,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
134
|
version: '0'
|
77
135
|
requirements: []
|
78
136
|
rubyforge_project:
|
79
|
-
rubygems_version: 1.8.
|
137
|
+
rubygems_version: 1.8.24
|
80
138
|
signing_key:
|
81
139
|
specification_version: 3
|
82
140
|
summary: Ruby PLC file library
|
data/Rakefile
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
require 'rake/testtask'
|
5
|
-
Rake::TestTask.new(:test) do |test|
|
6
|
-
test.libs << 'lib' << 'test'
|
7
|
-
test.pattern = 'test/**/test_*.rb'
|
8
|
-
test.verbose = true
|
9
|
-
end
|
10
|
-
|
11
|
-
begin
|
12
|
-
require 'rcov/rcovtask'
|
13
|
-
Rcov::RcovTask.new do |test|
|
14
|
-
test.libs << 'test'
|
15
|
-
test.pattern = 'test/**/test_*.rb'
|
16
|
-
test.verbose = true
|
17
|
-
end
|
18
|
-
rescue LoadError
|
19
|
-
task :rcov do
|
20
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
task :test => :check_dependencies
|
25
|
-
|
26
|
-
task :default => :test
|
27
|
-
|
28
|
-
require 'rake/rdoctask'
|
29
|
-
Rake::RDocTask.new do |rdoc|
|
30
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
31
|
-
|
32
|
-
rdoc.rdoc_dir = 'rdoc'
|
33
|
-
rdoc.title = "tallakt-plcutil #{version}"
|
34
|
-
rdoc.rdoc_files.include('README*')
|
35
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
36
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
require 'optparse'
|
4
|
-
require 'tallakt-plcutil/siemens/awlfile'
|
5
|
-
|
6
|
-
module PlcUtil
|
7
|
-
# Command line tool to read and output an awl file
|
8
|
-
class AwlPrettyPrintRunner
|
9
|
-
def initialize(args)
|
10
|
-
@awloptions = {}
|
11
|
-
@format = '%-11s %-40s%-10s%s'
|
12
|
-
@commentformat = '# %s / %s'
|
13
|
-
@output = nil
|
14
|
-
@symlistfile = nil
|
15
|
-
@no_block = false
|
16
|
-
|
17
|
-
option_parser.parse! args
|
18
|
-
if args.size != 1
|
19
|
-
show_help
|
20
|
-
exit
|
21
|
-
end
|
22
|
-
filename, = args
|
23
|
-
@awl = AwlFile.new filename, @awloptions
|
24
|
-
if @output
|
25
|
-
File.open @output, 'w' do |f|
|
26
|
-
print_to_file f
|
27
|
-
end
|
28
|
-
else
|
29
|
-
print_to_file $stdout
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def fix_name(name)
|
34
|
-
if @no_block
|
35
|
-
name.sub /^[^\.]*\./, ''
|
36
|
-
else
|
37
|
-
name
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def print_to_file(f)
|
42
|
-
@awl.each_tag do |tag| #|name, data_block_name, addr, comment, struct_comment, type|
|
43
|
-
require 'rubygems'
|
44
|
-
require 'pp'
|
45
|
-
f.puts @format % [
|
46
|
-
tag[:addr],
|
47
|
-
fix_name(tag[:name]),
|
48
|
-
tag[:type].to_s,
|
49
|
-
[tag[:comment], tag[:struct_comment]].compact! ? '' : (@commentformat % [tag[:comment], tag[:struct_comment]])
|
50
|
-
]
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def option_parser
|
55
|
-
OptionParser.new do |opts|
|
56
|
-
opts.banner = "Usage: awlpp [options] AWLFILE"
|
57
|
-
opts.on("-c", "--csv", String, "Output as CSV file") do
|
58
|
-
format = '%s;%s;%s;%s'
|
59
|
-
@commentformat = '%s'
|
60
|
-
end
|
61
|
-
opts.on("-n", "--no-block", String, "Dont use the datablock as part of the tag", "name") do
|
62
|
-
@no_block = true
|
63
|
-
end
|
64
|
-
opts.on("-s", "--symlist FILE", String, "Specify SYMLIST.DBF file from S7 project ") do |symlistfile|
|
65
|
-
@awloptions[:symlist] = symlistfile
|
66
|
-
end
|
67
|
-
opts.on("-b", "--block NAME=ADDR", String, "Define address of datablock without", "reading symlist") do |blockdef|
|
68
|
-
name, addr = blockdef.split(/=/)
|
69
|
-
@awloptions[:blocks] ||= {}
|
70
|
-
@awloptions[:blocks][name] = addr
|
71
|
-
end
|
72
|
-
opts.on("-o", "--output FILE", String, "Output to specified file instead of", "standard output") do |output|
|
73
|
-
@output = output
|
74
|
-
end
|
75
|
-
opts.on_tail("-h", "--help", "Show this message") do
|
76
|
-
puts opts
|
77
|
-
exit
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def show_help
|
83
|
-
puts option_parser
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
@@ -1,295 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
|
4
|
-
module PlcUtil
|
5
|
-
# Reads a Siemens Step 7 AWL file and creates single tags with addresses and comment
|
6
|
-
class AwlFile
|
7
|
-
attr_reader :symlist
|
8
|
-
|
9
|
-
def initialize(filename, options = {})
|
10
|
-
@types = {}
|
11
|
-
init_basic_types
|
12
|
-
@datablocks = []
|
13
|
-
@symlist = {}
|
14
|
-
|
15
|
-
if options[:symlist]
|
16
|
-
require 'rubygems'
|
17
|
-
require 'dbf' or raise 'Please install gem dbf to read symlist file'
|
18
|
-
|
19
|
-
raise 'Specified symlist file not found' unless File.exists? options[:symlist]
|
20
|
-
table = DBF::Table.new(options[:symlist])
|
21
|
-
table.each do |rec|
|
22
|
-
next unless rec
|
23
|
-
@symlist[rec.attributes['_skz']] = rec.attributes['_opiec'] # or _ophist or _datatyp
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
@symlist.merge!(options[:blocks] || {}) # User may override blocks
|
28
|
-
|
29
|
-
File.open filename do |f|
|
30
|
-
parse f
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def each_tag(options = {})
|
35
|
-
@datablocks.each do |var|
|
36
|
-
name = var.name
|
37
|
-
if options[:no_block]
|
38
|
-
name = nil
|
39
|
-
end
|
40
|
-
var.type.explode(Address.new(0, data_block_address(var.name)), name, options).each do |item|
|
41
|
-
ii = item.clone
|
42
|
-
ii[:type] = item[:type].downcase.to_sym
|
43
|
-
yield ii
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def data_block_address(name)
|
51
|
-
if @symlist.key? name
|
52
|
-
@symlist[name].gsub /\s+/, ''
|
53
|
-
else
|
54
|
-
nil
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def init_basic_types
|
59
|
-
types = {
|
60
|
-
'BOOL' => 1,
|
61
|
-
'BYTE' => 8,
|
62
|
-
'CHAR' => 8,
|
63
|
-
'DATE' => 2 * 8,
|
64
|
-
'DINT' => 4 * 8,
|
65
|
-
'DWORD' => 4 * 8,
|
66
|
-
'INT' => 2 * 8,
|
67
|
-
'REAL' => 4 * 8,
|
68
|
-
'S5TIME' => 2 * 8,
|
69
|
-
'TIME' => 4 * 8,
|
70
|
-
'TIME_OF_DAY' => 4 * 8,
|
71
|
-
'WORD' => 2 * 8,
|
72
|
-
'DATE_AND_TIME' => 4 * 8, # ??? only used in VAR_TEMP
|
73
|
-
'TIMER' => 1, # Only in FC calls
|
74
|
-
'CONT_C' => 125 * 8,
|
75
|
-
}
|
76
|
-
types.each {|name, size| add_type BasicType.new(name, size) }
|
77
|
-
end
|
78
|
-
|
79
|
-
def add_type(type)
|
80
|
-
@types[type.name] = type
|
81
|
-
end
|
82
|
-
|
83
|
-
def lookup_type(type)
|
84
|
-
raise "Could not find type '#{type}'" unless @types.key? type
|
85
|
-
@types[type]
|
86
|
-
end
|
87
|
-
|
88
|
-
def parse(file)
|
89
|
-
stack = []
|
90
|
-
in_array_decl = false # array decalrations are sometimed split on two separate lines
|
91
|
-
in_datablock_decl = false
|
92
|
-
tagname = start = stop = type = comment = nil
|
93
|
-
db_to_fb = {} # a list connecting a DB to the FB where it was used
|
94
|
-
file.each_line do |l|
|
95
|
-
l.chomp!
|
96
|
-
if in_array_decl
|
97
|
-
if l.match '^\s+\"?([A-Za-z0-9_]+)"?\s?;'
|
98
|
-
type = $1
|
99
|
-
stack.last.add Variable.new(tagname, ArrayType.new(@types[type], start..stop), comment)
|
100
|
-
end
|
101
|
-
in_array_decl = false
|
102
|
-
else
|
103
|
-
case l
|
104
|
-
# TODO should also cater for 'DB 90' type addresses
|
105
|
-
when /^TYPE "(.+?)"/
|
106
|
-
stack = [StructType.new($1, :datatype)]
|
107
|
-
add_type stack.first
|
108
|
-
when /^FUNCTION_BLOCK "(.+?)"/
|
109
|
-
stack = [StructType.new($1, :functionblock)]
|
110
|
-
add_type stack.first
|
111
|
-
when /^DATA_BLOCK ("(.+?)"|DB\s+(\d+))/
|
112
|
-
in_datablock_decl = true
|
113
|
-
name = $2 || ('DB' + $3)
|
114
|
-
stack = [StructType.new(name, :datablock)]
|
115
|
-
@datablocks << Variable.new(name, stack.last)
|
116
|
-
when /^VAR_TEMP/
|
117
|
-
s = StructType.new('VAR_TEMP', :anonymous)
|
118
|
-
stack = [s]
|
119
|
-
when /^\s*(\S+) : STRUCT /
|
120
|
-
in_datablock_decl = false
|
121
|
-
s = StructType.new('STRUCT', :anonymous)
|
122
|
-
stack.last.add Variable.new($1, s)
|
123
|
-
stack << stack.last.children.last.type
|
124
|
-
when /^BEGIN$/
|
125
|
-
in_datablock_decl = false
|
126
|
-
when /^\s+BEGIN/
|
127
|
-
stack.pop
|
128
|
-
# New variable in struct or data block
|
129
|
-
when /^\s+([A-Za-z0-9_ ]+) : "?([A-Za-z0-9_ ]+?)"?\s*(:=\s*[^;]+)?;(\s*\/\/(.*))?/
|
130
|
-
if stack.any?
|
131
|
-
tagname, type_name, comment = $1, $2, ($5 || '')
|
132
|
-
stack.last.add Variable.new(tagname, lookup_type(type_name), comment)
|
133
|
-
end
|
134
|
-
when /^\s+([A-Za-z0-9_]+) : ARRAY\s*\[(\d+)\D+(\d+) \] OF "?([A-Za-z0-9_]+)"?\s?;(\s*\/\/(.*))?/
|
135
|
-
tagname, start, stop, type, comment = $1, $2, $3, $4, $6
|
136
|
-
stack.last.add Variable.new(tagname, ArrayType.new(lookup_type(type), start..stop), comment)
|
137
|
-
when /^\s+([A-Za-z0-9_]+) : ARRAY\s*\[(\d+)\D+(\d+) \] OF(\s?\/\/(.*))?$/
|
138
|
-
tagname, start, stop, comment = $1, $2, $3, $4
|
139
|
-
in_array_decl = true
|
140
|
-
when /^"(.*?)"$/
|
141
|
-
# datablock definition is stored in FB definition
|
142
|
-
# remember which FB to use but assign the FB
|
143
|
-
# only after all blocks have been loaded
|
144
|
-
db_to_fb[stack.first.name] = $1 if in_datablock_decl
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# Update FB to DB use
|
150
|
-
@datablocks.each do |db|
|
151
|
-
if db_to_fb.key? db.name
|
152
|
-
fb = lookup_type db_to_fb[db.name]
|
153
|
-
fb.children.each {|child| db.type.add child } if fb.respond_to? :children
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def defined_type(type)
|
159
|
-
@types.key?(type.name)
|
160
|
-
end
|
161
|
-
|
162
|
-
|
163
|
-
class BasicType
|
164
|
-
attr_accessor :bit_size, :name
|
165
|
-
|
166
|
-
def initialize(name, bit_size)
|
167
|
-
@bit_size, @name = bit_size, name
|
168
|
-
end
|
169
|
-
|
170
|
-
def explode(start_addr, name, comment, struct_comment, options = {})
|
171
|
-
actual_start = start_addr.next_start bit_size
|
172
|
-
[{:addr => actual_start, :name => name,
|
173
|
-
:struct_comment => struct_comment, :comment => comment, :type => @name}]
|
174
|
-
end
|
175
|
-
|
176
|
-
def end_address(start_address)
|
177
|
-
start_address.next_start(bit_size).skip! bit_size
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
class StructType
|
184
|
-
attr_accessor :name, :children, :type
|
185
|
-
|
186
|
-
def initialize(name = 'STRUCT', type = :anonymous)
|
187
|
-
@name = name
|
188
|
-
@children = []
|
189
|
-
@type = type
|
190
|
-
end
|
191
|
-
|
192
|
-
def add(child)
|
193
|
-
raise 'Added nil child' unless child
|
194
|
-
raise 'Added nil child type' unless child.type
|
195
|
-
@children << child
|
196
|
-
end
|
197
|
-
|
198
|
-
def end_address(start_address)
|
199
|
-
addr = start_address.next_start
|
200
|
-
@children.each do |child|
|
201
|
-
addr = child.type.end_address addr
|
202
|
-
end
|
203
|
-
addr.next_start!
|
204
|
-
end
|
205
|
-
|
206
|
-
def explode(start_addr, name, comment = nil, struct_comment = nil, options = {})
|
207
|
-
addr = start_addr.next_start
|
208
|
-
exploded = []
|
209
|
-
@children.each do |child|
|
210
|
-
|
211
|
-
exploded += child.type.explode(addr, [name, child.name].compact.join('.'), child.comment,
|
212
|
-
type == :datablock ? child.comment : struct_comment, options)
|
213
|
-
addr = child.type.end_address addr
|
214
|
-
end
|
215
|
-
exploded
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
class ArrayType
|
220
|
-
attr_accessor :range, :type
|
221
|
-
|
222
|
-
def initialize(type, range)
|
223
|
-
raise 'Added nil array type' unless type
|
224
|
-
raise 'Added nil array range' unless range
|
225
|
-
@range, @type = range, type
|
226
|
-
end
|
227
|
-
|
228
|
-
def name
|
229
|
-
'ARRAY'
|
230
|
-
end
|
231
|
-
|
232
|
-
def end_address(start_address)
|
233
|
-
addr = start_address.next_start
|
234
|
-
range.each do
|
235
|
-
addr = type.end_address addr
|
236
|
-
end
|
237
|
-
addr
|
238
|
-
end
|
239
|
-
|
240
|
-
def explode(start_addr, name, comment, struct_comment, options = {})
|
241
|
-
exploded = []
|
242
|
-
addr = start_addr.next_start
|
243
|
-
range.to_a.each_with_index do |v, i|
|
244
|
-
exploded += type.explode(addr, name + '[' + v.to_s + ']', comment, struct_comment, options = {})
|
245
|
-
addr = type.end_address(addr)
|
246
|
-
end
|
247
|
-
exploded
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
class Variable
|
252
|
-
attr_accessor :name, :type, :comment
|
253
|
-
|
254
|
-
def initialize(name, type, comment = nil)
|
255
|
-
@name, @type, @comment= name, type, comment
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
class Address
|
260
|
-
attr_accessor :bit_addr, :data_block_addr
|
261
|
-
|
262
|
-
def initialize(bit_addr, data_block_addr = nil)
|
263
|
-
@bit_addr, @data_block_addr = bit_addr, data_block_addr
|
264
|
-
end
|
265
|
-
|
266
|
-
def to_s
|
267
|
-
(data_block_addr || 'DB???') + ',' + byte.to_s + '.' + bit.to_s
|
268
|
-
end
|
269
|
-
|
270
|
-
def bit
|
271
|
-
@bit_addr % 8
|
272
|
-
end
|
273
|
-
|
274
|
-
def byte
|
275
|
-
@bit_addr / 8
|
276
|
-
end
|
277
|
-
|
278
|
-
def next_start!(bit_size = 2 * 8)
|
279
|
-
bs = [bit_size, 2 * 8].min # bools use bits, chars/bytes one byte others two byte rounding of start address
|
280
|
-
rest = @bit_addr % bs
|
281
|
-
@bit_addr += (bs - rest) if rest > 0
|
282
|
-
self
|
283
|
-
end
|
284
|
-
|
285
|
-
def next_start(bit_size = 2 * 8)
|
286
|
-
self.clone.next_start! bit_size
|
287
|
-
end
|
288
|
-
|
289
|
-
def skip!(bit_count)
|
290
|
-
@bit_addr += bit_count
|
291
|
-
self
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|