automotive-ecu 0.3.6 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab586bfaab8ab1d647a8f0f76cd247798baba7e10e207a9e03173a854fb13817
4
- data.tar.gz: 39cf49dca991aa8966dd679914d9d88f05585e7b0a34fdff29a8783e2eae19d9
3
+ metadata.gz: 9e5446929b1a5bedccd68a5e669c148a0386663feda51949c8ebaee2fe143db3
4
+ data.tar.gz: ede842831f425f87c3092be6cd72185b82f5eb8b2a7d50ccc635ba23ec85b661
5
5
  SHA512:
6
- metadata.gz: 21a979a28ff42b637574725b54a0dc46ff5a4659f0a26bdb50ec0e6a81c0262a46d14891af2f09f8846a545f1decb6e2994c4de92dbf05b8f0171c6f946b27f5
7
- data.tar.gz: 2f2026c2d122d4ecd2295ff941c4fb397fe5b20383ab927e731a80178733d69e86a23343429abf751c8beab39b8483bd8c35ce62049a3af0302436668a986d6d
6
+ metadata.gz: 4b408ab9515e6909db8d4f9efa029ffd9cfa498afb9f5d5ecbff14a6283338741b6d8cc931db4b2b5eec52096ecb77b20e8072890d7e229b8b0f1fb1f176e02a
7
+ data.tar.gz: 7a9bed9c8d5568bccd4423f0578c28062e8e769b4a0c5bc44770d2356307bc1f8e33dcbebac0d430e48a6f8578021032d2c79c7072c42194828b269e4a52b445
@@ -1,238 +1,159 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "dcm_lexer"
4
- require_relative "dcm_parser_error"
5
- require "set"
3
+ require_relative "dcm_scanner"
6
4
 
7
5
  class Ecu
8
6
  class LabelList
7
+ # Recursive-descent parser for DCM 2.0:
8
+ #
9
+ # file := comment* HEADER (comment | functions | label)*
10
+ # functions := FUNKTIONEN (FKT ...)* END
11
+ # label := KIND name dimensions property* END
12
+ # property := KEYWORD value* EOL
13
+ #
14
+ # Comments before the header are #headers, those after it #subheaders.
9
15
  class DcmParser
16
+ LABEL_KINDS = {
17
+ "FESTWERT" => Festwert,
18
+ "FESTWERTEBLOCK" => Festwerteblock,
19
+ "KENNLINIE" => Kennlinie,
20
+ "GRUPPENKENNLINIE" => Gruppenkennlinie,
21
+ "FESTKENNLINIE" => Festkennlinie,
22
+ "KENNFELD" => Kennfeld,
23
+ "GRUPPENKENNFELD" => Gruppenkennfeld,
24
+ "FESTKENNFELD" => Festkennfeld,
25
+ "STUETZSTELLENVERTEILUNG" => Stuetzstellenverteilung,
26
+ }.freeze
27
+
28
+ FUNCTIONS = "FUNKTIONEN"
29
+ FUNCTION = "FKT"
30
+ END_LABEL = "END"
31
+
32
+ # keyword => [label attribute, value reader of DcmScanner]
33
+ PROPERTIES = {
34
+ "LANGNAME" => [:description, :text],
35
+ "FUNKTION" => [:function, :identifier],
36
+ "DISPLAYNAME" => [nil, :identifier], # repeats the label name
37
+ "EINHEIT_X" => [:xunit, :text],
38
+ "EINHEIT_Y" => [:yunit, :text],
39
+ "EINHEIT_W" => [:unit, :text],
40
+ "ST/X" => [:xvalue, :numbers],
41
+ "ST/Y" => [:yvalue, :numbers],
42
+ "WERT" => [:value, :numbers],
43
+ "ST_TX/X" => [:xvalue, :texts],
44
+ "ST_TX/Y" => [:yvalue, :texts],
45
+ "TEXT" => [:value, :texts],
46
+ }.freeze
47
+
48
+ # Readers of values that may continue on following lines.
49
+ LIST_READERS = %i[numbers texts].freeze
50
+
51
+ attr_reader :headers, :subheaders, :labels
10
52
 
11
- ARY_PROPERTIES = Set.new([:xvalue, :yvalue, :value]).freeze
12
- KEY_MAPPING = {
13
- "ST/X": :xvalue,
14
- "ST/Y": :yvalue,
15
- "WERT": :value,
16
- "ST_TX/X": :xvalue,
17
- "ST_TX/Y": :yvalue,
18
- "TEXT": :value,
19
- "FUNKTION": :function,
20
- "DISPLAYNAME": :displayname,
21
- "EINHEIT_X": :xunit,
22
- "EINHEIT_Y": :yunit,
23
- "EINHEIT_W": :unit,
24
- "LANGNAME": :description,
25
- }
26
-
27
- attr_reader :lexer, :state, :labels, :headers, :subheaders
28
53
  def initialize(doc)
29
- @lexer = DcmLexer.new(doc)
30
- @state = :PRE_HEADER
31
- @labels = []
54
+ @scanner = DcmScanner.new(doc)
32
55
  @headers = []
33
56
  @subheaders = []
34
- @properties = {}
35
- reset_label!
57
+ @labels = []
36
58
  end
37
59
 
38
60
  def call
39
- while tok = lexer.next_token
40
- begin
41
- case @state
42
- when :PRE_HEADER
43
- case tok
44
- when :HEADER then next_state(:POST_HEADER)
45
- when :COMMENT then add_header(lexer.token_value)
46
- when :NEWLINE then # noop
47
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
48
- end
49
- when :POST_HEADER
50
- case tok
51
- when :FUNKTIONEN then next_state(:FUNCTIONS_CONTENT)
52
- when :FESTWERT then start_label(Festwert)
53
- when :FESTWERTEBLOCK then start_label(Festwerteblock)
54
- when :KENNLINIE then start_label(Kennlinie)
55
- when :GRUPPENKENNLINIE then start_label(Gruppenkennlinie)
56
- when :FESTKENNLINIE then start_label(Festkennlinie)
57
- when :KENNFELD then start_label(Kennfeld)
58
- when :GRUPPENKENNFELD then start_label(Gruppenkennfeld)
59
- when :FESTKENNFELD then start_label(Festkennfeld)
60
- when :STUETZSTELLENVERTEILUNG then start_label(Stuetzstellenverteilung)
61
- when :COMMENT then add_subheader(lexer.token_value)
62
- when :NEWLINE then # noop
63
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
64
- end
65
- when :FUNCTIONS_CONTENT
66
- case tok
67
- when :FKT then next_state(:FUNCTION_CONTENT)
68
- when :NEWLINE then # noop
69
- when :END then next_state(:POST_HEADER)
70
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
71
- end
72
- when :FUNCTION_CONTENT
73
- case tok
74
- when :IDENTIFIER then # append that shit?
75
- when :QUOTED_TEXT then # append that shit as well?
76
- when :NEWLINE then next_state(:FUNCTIONS_CONTENT)
77
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
78
- end
79
- when :LABEL_HEADLINE
80
- case tok
81
- when :IDENTIFIER then add_name(lexer.token_value)
82
- when :DIMENSIONS_SEP then # noop, order handled by #add_dimension
83
- when :INT then add_dimension(lexer.token_value.to_i)
84
- when :NEWLINE then next_state(:LABEL_CONTENT)
85
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
86
- end
87
- when :LABEL_CONTENT
88
- case tok
89
- when :WERT then start_property(:WERT, :NUMERIC_PROPERTY)
90
- when :TEXT then start_property(:TEXT, :TEXT_PROPERTY)
91
- when :EINHEIT_X then start_property(:EINHEIT_X, :TEXT_PROPERTY)
92
- when :EINHEIT_Y then start_property(:EINHEIT_Y, :TEXT_PROPERTY)
93
- when :EINHEIT_W then start_property(:EINHEIT_W, :TEXT_PROPERTY)
94
- when :LANGNAME then start_property(:LANGNAME, :TEXT_PROPERTY)
95
- when :FUNKTION then start_property(:FUNKTION, :ID_PROPERTY)
96
- when :DISPLAYNAME then start_property(:DISPLAYNAME, :ID_PROPERTY)
97
- when :END then labels << finish_label
98
- when :"ST/X" then start_property(:"ST/X", :NUMERIC_PROPERTY)
99
- when :"ST/Y" then start_property(:"ST/Y", :NUMERIC_PROPERTY)
100
- when :"ST_TX/X" then start_property(:"ST_TX/X", :TEXT_PROPERTY)
101
- when :"ST_TX/Y" then start_property(:"ST_TX/Y", :TEXT_PROPERTY)
102
- when :NEWLINE then # noop, DCM badly formatted
103
- when :COMMENT then # noop, some programs add SST in comments
104
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
105
- end
106
- when :NUMERIC_PROPERTY
107
- case tok
108
- when :FLOAT then append_property(lexer.token_value.to_f)
109
- when :INT then append_property(lexer.token_value.to_i)
110
- when :NEWLINE then finish_property
111
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
112
- end
113
- when :TEXT_PROPERTY
114
- case tok
115
- when :QUOTED_TEXT then append_property(lexer.quoted_value)
116
- when :NEWLINE then finish_property
117
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
118
- end
119
- when :ID_PROPERTY
120
- case tok
121
- when :IDENTIFIER then append_property(lexer.token_value)
122
- when :NEWLINE then finish_property
123
- else raise DcmParserError.new("Unexpected token #{debug_token(tok)} (state: #{@state})", lexer)
124
- end
125
- end
126
- rescue DcmParserError
127
- raise
128
- rescue StandardError => e
129
- raise DcmParserError.new("#{e.message} on #{e.backtrace.first} (state: #{@state})", lexer)
130
- end
131
- end
132
- fail_on_truncation!
61
+ read_comments(headers)
62
+ @scanner.header or raise @scanner.error("Expected #{DcmScanner::HEADER}")
63
+
64
+ read_blocks
133
65
  [labels, headers, subheaders]
134
66
  end
135
67
 
136
- # A well-formed file ends between labels, not inside one.
137
- def fail_on_truncation!
138
- return if %i[PRE_HEADER POST_HEADER].include?(@state)
68
+ private
139
69
 
140
- raise DcmParserError.new("Unexpected end of file (state: #{@state})", lexer)
141
- end
70
+ def read_comments(into)
71
+ loop do
72
+ @scanner.skip_blank_lines
73
+ comment = @scanner.comment or return
142
74
 
143
- def debug_token(tok)
144
- if tok == :UNKNOWN_CHAR
145
- "UNKNOWN_CHAR: #{lexer.token_value}"
146
- else
147
- tok
75
+ into << comment
148
76
  end
149
77
  end
150
78
 
151
- def next_state(newstate)
152
- yield if block_given?
153
- ensure
154
- @state = newstate
155
- end
79
+ def read_blocks
80
+ loop do
81
+ read_comments(subheaders)
82
+ return if @scanner.at_end?
156
83
 
157
- def add_header(str)
158
- @headers << str[1..].strip
159
- end
160
-
161
- def add_subheader(str)
162
- @subheaders << str[1..].strip
163
- end
84
+ start = @scanner.pos
85
+ keyword = @scanner.keyword
86
+ next skip_functions if keyword == FUNCTIONS
164
87
 
165
- def start_label(constructor)
166
- next_state(:LABEL_HEADLINE) do
167
- @constructor = constructor
88
+ kind = LABEL_KINDS[keyword] or raise @scanner.error("Unexpected #{keyword || "text"}", start)
89
+ labels << read_label(kind, start)
168
90
  end
169
91
  end
170
92
 
171
- def finish_label
172
- next_state(:POST_HEADER) do
173
- @properties.delete(:displayname)
174
- @constructor.new(**@properties).tap { reset_label! }
175
- end
176
- end
93
+ # The function list is not used, but must be well-formed.
94
+ def skip_functions
95
+ @scanner.end_of_line
177
96
 
178
- def add_name(str)
179
- fail "Duplicate name" if @properties.key?(:name)
97
+ loop do
98
+ @scanner.skip_noise
99
+ at = @scanner.pos
100
+ keyword = @scanner.keyword
180
101
 
181
- @properties[:name] = str
102
+ case keyword
103
+ when END_LABEL then return @scanner.end_of_line
104
+ when FUNCTION then @scanner.skip_line
105
+ else raise @scanner.error("Unexpected #{keyword || "end of file"} in #{FUNCTIONS}", at)
106
+ end
107
+ end
182
108
  end
183
109
 
184
- def add_dimension(n)
185
- fail "Wrong order name/dimensions" unless @properties.key?(:name)
186
- fail "Too many dimensions" if @properties.key?(:xdim) && @properties.key?(:ydim)
110
+ def read_label(kind, start)
111
+ props = { name: @scanner.name }
112
+ props.merge!(@scanner.dimensions)
187
113
 
188
- if @properties.key?(:xdim)
189
- @properties[:ydim] = n
190
- else
191
- @properties[:xdim] = n
192
- end
193
- end
114
+ loop do
115
+ @scanner.skip_noise
116
+ at = @scanner.pos
117
+ keyword = @scanner.keyword or raise @scanner.error(missing_property_message, at)
118
+ break @scanner.end_of_line if keyword == END_LABEL
194
119
 
195
- def start_property(key, newstate)
196
- next_state(newstate) do
197
- @key = KEY_MAPPING[key]
198
- @is_ary = ARY_PROPERTIES.include?(@key)
199
- if @is_ary
200
- @properties[@key] ||= []
201
- @value = @properties[@key] # alias: append_property writes directly to target
202
- else
203
- @value = nil
204
- end
120
+ read_property(keyword, props, at)
205
121
  end
122
+
123
+ build(kind, props, start)
206
124
  end
207
125
 
208
- def append_property(value)
209
- if @is_ary
210
- @value << value
211
- else
212
- fail "Multiple definitions for #{@key}" if @value
126
+ def read_property(keyword, props, at)
127
+ attribute, reader = PROPERTIES[keyword]
128
+ raise @scanner.error("Unknown property #{keyword}", at) unless reader
213
129
 
214
- @value = value
215
- end
216
- end
130
+ value = @scanner.public_send(reader)
131
+ return unless attribute
217
132
 
218
- def finish_property
219
- next_state(:LABEL_CONTENT) do
220
- next if @is_ary # values already in @properties[@key] via @value alias
133
+ store(props, attribute, reader, value, at)
134
+ end
221
135
 
222
- fail "Multiple property lines not allowed for #{@key}" if @properties.key?(@key)
223
- @properties[@key] = @value
136
+ # Lists such as ST/X may span several lines, single values may not.
137
+ def store(props, attribute, reader, value, at)
138
+ if LIST_READERS.include?(reader)
139
+ (props[attribute] ||= []).concat(value)
140
+ return
224
141
  end
142
+
143
+ raise @scanner.error("Duplicate #{attribute}", at) if props.key?(attribute)
144
+
145
+ props[attribute] = value
225
146
  end
226
147
 
227
- def reset_label!
228
- @constructor = nil
229
- @properties.clear
230
- @key = nil
231
- @is_ary = false
232
- @value = nil
148
+ def build(kind, props, start)
149
+ kind.new(**props)
150
+ rescue ArgumentError => e
151
+ raise @scanner.error("#{props[:name]}: #{e.message.lines.first.strip}", start)
233
152
  end
234
153
 
154
+ def missing_property_message
155
+ @scanner.at_end? ? "Unexpected end of file" : "Expected a property or #{END_LABEL}"
156
+ end
235
157
  end
236
-
237
158
  end
238
159
  end
@@ -4,10 +4,12 @@ class Ecu
4
4
  CTXLENGHT = 5
5
5
 
6
6
  attr_reader :doc, :lineno
7
- def initialize(msg, lexer)
7
+
8
+ # offset: byte position in doc where parsing failed
9
+ def initialize(msg, doc, offset)
8
10
  @msg = msg
9
- @doc = lexer.doc
10
- @lineno = lexer.lineno
11
+ @doc = doc
12
+ @lineno = doc.byteslice(0, offset).count("\n") + 1
11
13
  end
12
14
 
13
15
  def message = @msg
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+ require_relative "dcm_parser_error"
5
+
6
+ class Ecu
7
+ class LabelList
8
+ # Reads DCM text one construct at a time. Each reader consumes what it
9
+ # returns, including the line break, and raises DcmParserError when the
10
+ # input is malformed.
11
+ #
12
+ # Values must be separated by blanks, also from their keyword:
13
+ #
14
+ # WERT 1 2.5 ok
15
+ # WERT-1 rejected
16
+ # ST/X 1.5.2 rejected
17
+ class DcmScanner
18
+ DIMENSION_NAMES = %i[xdim ydim].freeze
19
+ DIMENSION_SEP = "@"
20
+ MAX_DIMENSIONS = DIMENSION_NAMES.size
21
+
22
+ HEADER = "KONSERVIERUNG_FORMAT 2.0"
23
+ LINE_END = /(?:\r\n|\n|\r)/
24
+ EOL = /[ \t]*(?:\r\n|\n|\r|\z)/
25
+ BLANK_LINES = /(?:[ \t]*#{LINE_END})*(?:[ \t]+\z)?/
26
+ NOISE = /(?:[ \t]*(?:\*[^\r\n]*)?#{LINE_END})*/
27
+ COMMENT = /[ \t]*\*([^\r\n]*)(?:\r\n|\n|\r|\z)/
28
+ KEYWORD = %r{[ \t]*([A-Z][A-Z_/]*)}
29
+ IDENTIFIER = /[ \t]+([A-Za-z][A-Za-z0-9_.\[\]]*)/
30
+ QUOTED = /[ \t]+"([^"]*)"/
31
+ REST_OF_LINE = /[^\r\n]*/
32
+ NUMBER_CHARS = /\A[-+.eE0-9 \t]*\z/
33
+ TRAILING_DOT = /\A[-+]?[0-9]+\.\z/ # "9." is a valid DCM number, Ruby disagrees
34
+
35
+ attr_reader :doc
36
+
37
+ def initialize(doc)
38
+ @doc = doc
39
+ @s = StringScanner.new(doc)
40
+ end
41
+
42
+ def pos = @s.pos
43
+ def at_end? = @s.eos?
44
+
45
+ def error(message, at = pos)
46
+ DcmParserError.new(message, doc, at)
47
+ end
48
+
49
+ def header
50
+ @s.skip(HEADER) or return false
51
+
52
+ end_of_line
53
+ true
54
+ end
55
+
56
+ def skip_blank_lines
57
+ @s.skip(BLANK_LINES)
58
+ end
59
+
60
+ # Blank and comment lines, which carry no data inside a label.
61
+ def skip_noise
62
+ @s.skip(NOISE)
63
+ end
64
+
65
+ # Text of the next comment line without the leading "*", or nil.
66
+ def comment
67
+ @s.skip(COMMENT) ? @s[1].strip : nil
68
+ end
69
+
70
+ # Upper-case word such as WERT, ST_TX/X or END, or nil.
71
+ def keyword
72
+ @s.skip(KEYWORD) ? @s[1] : nil
73
+ end
74
+
75
+ def end_of_line
76
+ return if @s.skip(EOL)
77
+
78
+ raise error("Unexpected #{@s.rest.lines.first.strip.inspect}")
79
+ end
80
+
81
+ def skip_line
82
+ @s.skip(REST_OF_LINE)
83
+ end_of_line
84
+ end
85
+
86
+ # Label name, which is followed by the dimensions on the same line.
87
+ def name
88
+ @s.skip(IDENTIFIER) or raise error("Expected an identifier")
89
+ @s[1]
90
+ end
91
+
92
+ def identifier
93
+ value = name
94
+ end_of_line
95
+ value
96
+ end
97
+
98
+ # Exactly one quoted text, e.g. ` "kg"`.
99
+ def text
100
+ at = pos
101
+ values = texts
102
+ raise error("Expected one quoted text", at) unless values.size == 1
103
+
104
+ values.first
105
+ end
106
+
107
+ # Any number of quoted texts, e.g. ` "a" "b"`.
108
+ def texts
109
+ values = []
110
+ values << @s[1] while @s.skip(QUOTED)
111
+ end_of_line
112
+ values
113
+ end
114
+
115
+ # Any number of integers and floats, e.g. ` 1 2.5 -3e2`.
116
+ def numbers
117
+ at = pos
118
+ line = @s.scan(REST_OF_LINE)
119
+
120
+ unless line.empty? || line.start_with?(" ", "\t")
121
+ raise error("Whitespace required before value", at)
122
+ end
123
+ raise error("Invalid number in #{line.strip.inspect}", at) unless line.match?(NUMBER_CHARS)
124
+
125
+ values = line.split
126
+ values.map! { number(_1) or raise error("Invalid number #{_1.inspect}", at) }
127
+ end_of_line
128
+ values
129
+ end
130
+
131
+ # Label dimensions as { xdim:, ydim: }, e.g. ` 3 @ 4` or ` 3`.
132
+ def dimensions
133
+ at = pos
134
+ tokens = @s.scan(REST_OF_LINE).split
135
+ tokens.delete(DIMENSION_SEP)
136
+ raise error("Too many dimensions", at) if tokens.size > MAX_DIMENSIONS
137
+
138
+ ints = tokens.map { Integer(_1, 10, exception: false) or raise error("Invalid dimension #{_1.inspect}", at) }
139
+ end_of_line
140
+ DIMENSION_NAMES.first(ints.size).zip(ints).to_h
141
+ end
142
+
143
+ private
144
+
145
+ def number(token)
146
+ Integer(token, 10, exception: false) ||
147
+ Float(token, exception: false) ||
148
+ (token.to_f if TRAILING_DOT.match?(token))
149
+ end
150
+ end
151
+ end
152
+ end
@@ -63,6 +63,9 @@ class Ecu
63
63
  LabelList.new(labels, @unified_headers, @unified_subheaders)
64
64
  end
65
65
 
66
+ def headers = @unified_headers
67
+ def subheaders = @unified_subheaders
68
+
66
69
  private
67
70
 
68
71
  attr_reader :left, :right
@@ -44,6 +44,9 @@ class Ecu
44
44
  @unified_subheaders
45
45
  end
46
46
 
47
+ def headers = @unified_headers
48
+ def subheaders = @unified_subheaders
49
+
47
50
  private
48
51
 
49
52
  attr_reader :left, :right
data/lib/ecu/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Ecu
2
- VERSION = "0.3.6"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: automotive-ecu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonas Mueller
@@ -99,9 +99,9 @@ files:
99
99
  - lib/ecu/interfaces/a2l/label_list.rb
100
100
  - lib/ecu/interfaces/a2l/signal_list.rb
101
101
  - lib/ecu/interfaces/dbc/signal_list.rb
102
- - lib/ecu/interfaces/dcm/dcm_lexer.rb
103
102
  - lib/ecu/interfaces/dcm/dcm_parser.rb
104
103
  - lib/ecu/interfaces/dcm/dcm_parser_error.rb
104
+ - lib/ecu/interfaces/dcm/dcm_scanner.rb
105
105
  - lib/ecu/interfaces/dcm/festkennfeld.rb
106
106
  - lib/ecu/interfaces/dcm/festkennlinie.rb
107
107
  - lib/ecu/interfaces/dcm/festwert.rb
@@ -164,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
164
  - !ruby/object:Gem::Version
165
165
  version: '0'
166
166
  requirements: []
167
- rubygems_version: 4.0.16
167
+ rubygems_version: 4.0.23
168
168
  specification_version: 4
169
169
  summary: A simple API for managing automotive ecu signals and labels
170
170
  test_files: []
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "strscan"
4
-
5
- class Ecu
6
- class LabelList
7
- class DcmLexer
8
- attr_reader :doc
9
- def initialize(doc)
10
- @doc = doc
11
- @scan = StringScanner.new(doc)
12
- end
13
-
14
- KEYWORDS = [
15
- "FUNKTIONEN", "FESTWERT", "FESTWERTEBLOCK", "KENNLINIE", "GRUPPENKENNLINIE", "FESTKENNLINIE",
16
- "KENNFELD", "GRUPPENKENNFELD", "FESTKENNFELD", "STUETZSTELLENVERTEILUNG",
17
- "FKT",
18
- "ST/X", "ST/Y", "WERT", "ST_TX/X", "ST_TX/Y", "TEXT",
19
- "FUNKTION",
20
- "EINHEIT_X", "EINHEIT_Y", "EINHEIT_W",
21
- "LANGNAME", "DISPLAYNAME",
22
- "END",
23
- ].freeze
24
-
25
- HEADER = "KONSERVIERUNG_FORMAT 2.0"
26
- WHITESPACE = %r{ [ \t]+ }x
27
- NEWLINE = %r{ \r\n|\n|\r }x
28
- COMMENT = %r{ ^\*.*$ }x
29
- DIMENSIONS_SEP = %r{ @ }x
30
- QUOTED_TEXT = %r{ "[^"]*" }x
31
- IDENTIFIER = %r{ [A-Za-z][A-Za-z0-9_\.\[\]]* }x
32
- UNSIGNED_INT = %r{ [0]|[1-9][0-9]* }x
33
- INT = %r{ [-]?#{UNSIGNED_INT} }x
34
- FLOAT_EXP = %r{ [eE][+-]?[0-9]+ }x
35
- FLOAT = %r{
36
- [-+]?
37
- (?:
38
- #{UNSIGNED_INT}?[.][0-9]+#{FLOAT_EXP} | # 1.23e10 or .45e3
39
- #{UNSIGNED_INT} #{FLOAT_EXP} | # 3e4
40
- #{UNSIGNED_INT}?[.][0-9]+ | # 7.3 or .50
41
- #{UNSIGNED_INT} [.][0-9]* # 3.02 or 9.
42
- )
43
- }x
44
- KW_RE = /#{Regexp.union(KEYWORDS.sort)}\b/
45
- KW_TABLE = Hash[KEYWORDS.map { [_1, _1.upcase.to_sym] }]
46
-
47
-
48
- def next_token
49
- # This is a hot path during DCM parsing. Make sure to optimize
50
- # here are far as possible
51
-
52
- @scan.skip(WHITESPACE)
53
-
54
- return if @scan.eos?
55
-
56
- case @doc.getbyte(@scan.pos)
57
- when 10, 13 then @scan.skip(NEWLINE) && :NEWLINE # \n \r
58
- when 34 then @scan.skip(QUOTED_TEXT) && :QUOTED_TEXT # "
59
- when 42 then @scan.skip(COMMENT) && :COMMENT # *
60
- when 64 then @scan.skip(DIMENSIONS_SEP) && :DIMENSIONS_SEP # @
61
- when 43, 45, 46, 48..57 # + - . 0-9
62
- if @scan.skip(FLOAT) then :FLOAT
63
- elsif @scan.skip(INT) then :INT
64
- else @scan.getch; :UNKNOWN_CHAR
65
- end
66
- else
67
- if s = @scan.scan(KW_RE) then KW_TABLE[s]
68
- elsif @scan.skip(HEADER) then :HEADER
69
- elsif @scan.skip(IDENTIFIER) then :IDENTIFIER
70
- else @scan.getch; :UNKNOWN_CHAR
71
- end
72
- end
73
- end
74
-
75
- def token_value
76
- @scan.matched
77
- end
78
-
79
- # Extracts quoted-text content without surrounding quotes in a single allocation.
80
- def quoted_value
81
- @scan.string.byteslice(@scan.pos - @scan.matched_size + 1, @scan.matched_size - 2)
82
- end
83
-
84
- def lineno
85
- @doc.byteslice(0, @scan.pos).count("\n") +
86
- (@scan.beginning_of_line? ? 0 : 1)
87
- end
88
- end
89
- end
90
- end