automotive-ecu 0.2.0 → 0.2.2

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: d05b1f41faab9eb4a9ffb33697b015f0242d64450f25bedf6ce47aef2aac08ca
4
- data.tar.gz: ecdc24755f8244e51a2bc20ee9efef50f3e40be5f6a2d5f678d5bdea462f5c8b
3
+ metadata.gz: 75ae19b7bbf77cd45e42cc9e6b5138d19e85101df900fe844e3102bf18ac2c79
4
+ data.tar.gz: ecbc4929534d9245169ca0eaa7518df4fb18a08727b66244f4b6382a053af827
5
5
  SHA512:
6
- metadata.gz: 1b92c416a9dacb5dd9c28afd30bdf9de68e31390977612752bf23365cf9a9b93894d7e4641bb5629832debb237d549fb4f38c475b2b55ad812c271b99b193e52
7
- data.tar.gz: f8548dd035ac77f453d2b36717f398107d16207febfb886bc26964a6c84ef1582b0e41ea1039fed4b3afc0ebd48a8230e21eb47255d1064c71ae6f5ab90c9e74
6
+ metadata.gz: ff02d2b04386b8129d249be984934bcda8d53bd5f24057541f36c555152cbda2b008ac9ee1d4bdfec90d67fdcbc2c0455c0eaac2d5df524f44b857d21cad0cf2
7
+ data.tar.gz: fef42ccdfe5947851955aad5fc2655cabd047576268310da2cc1b6606e3c4390f6db91907a21b24710140149dfafd8b74670ac2aa701009a9ba8c9f4625d872b
data/bin/sandbox ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/ecu/combined_list"
4
+
5
+ begin
6
+ list = Ecu::CombinedList.from_file('spec/fixtures/lab/advanced.lab')
7
+ list.labels.each do |item|
8
+ puts item
9
+ end
10
+ list.signals.each do |item|
11
+ puts item.description
12
+ end
13
+ rescue Ecu::LabParserError => e
14
+ puts e.message
15
+ puts e.context
16
+ end
@@ -6,6 +6,15 @@ require_relative "interfaces/lab/combined_list"
6
6
  class Ecu
7
7
  class CombinedList
8
8
 
9
+ def self.from_file(file_path)
10
+ case File.extname(file_path)
11
+ when ".lab" then self.from_lab(File.read_encoded(file_path))
12
+ when ".a2l" then self.from_a2l(File.read_encoded(file_path))
13
+ when ".dbc" then self.from_dbc(File.read_encoded(file_path))
14
+ else fail "Unknown file extension: #{file_path}!"
15
+ end
16
+ end
17
+
9
18
  def initialize(signals=SignalList.new, labels=LabelList.new, headers=[], subheaders=[])
10
19
  @signals = signals
11
20
  @labels = labels
@@ -39,7 +39,7 @@ class Ecu
39
39
  case tok
40
40
  in :HEADER then next_state(:POST_HEADER)
41
41
  in :COMMENT then add_header(lexer.token_value)
42
- in :NEWLINE
42
+ in :NEWLINE then # noop
43
43
  end
44
44
  in :POST_HEADER
45
45
  case tok
@@ -77,11 +77,7 @@ class Ecu
77
77
  end
78
78
  in :LABEL_CONTENT
79
79
  case tok
80
- in :"ST/X" then start_property(:"ST/X", :NUMERIC_PROPERTY)
81
- in :"ST/Y" then start_property(:"ST/Y", :NUMERIC_PROPERTY)
82
80
  in :WERT then start_property(:WERT, :NUMERIC_PROPERTY)
83
- in :"ST_TX/X" then start_property(:"ST_TX/X", :TEXT_PROPERTY)
84
- in :"ST_TX/Y" then start_property(:"ST_TX/Y", :TEXT_PROPERTY)
85
81
  in :TEXT then start_property(:TEXT, :TEXT_PROPERTY)
86
82
  in :EINHEIT_X then start_property(:EINHEIT_X, :TEXT_PROPERTY)
87
83
  in :EINHEIT_Y then start_property(:EINHEIT_Y, :TEXT_PROPERTY)
@@ -90,6 +86,10 @@ class Ecu
90
86
  in :FUNKTION then start_property(:FUNKTION, :ID_PROPERTY)
91
87
  in :DISPLAYNAME then start_property(:DISPLAYNAME, :ID_PROPERTY)
92
88
  in :END then labels << finish_label
89
+ in :"ST/X" then start_property(:"ST/X", :NUMERIC_PROPERTY)
90
+ in :"ST/Y" then start_property(:"ST/Y", :NUMERIC_PROPERTY)
91
+ in :"ST_TX/X" then start_property(:"ST_TX/X", :TEXT_PROPERTY)
92
+ in :"ST_TX/Y" then start_property(:"ST_TX/Y", :TEXT_PROPERTY)
93
93
  in :NEWLINE then # noop, DCM badly formatted
94
94
  in :COMMENT then # noop, some programs add SST in comments
95
95
  end
@@ -21,6 +21,13 @@ class Ecu
21
21
  .then { new(*_1, true) }
22
22
  end
23
23
 
24
+ def self.from_dcm_partial(str)
25
+ str
26
+ .then { DCM_HEADER + "\n\n" + _1 }
27
+ .then { from_dcm(_1) }
28
+ .first
29
+ end
30
+
24
31
  def to_dcm(indented=false)
25
32
  out = []
26
33
 
@@ -0,0 +1,9 @@
1
+ require "json"
2
+
3
+ class Ecu
4
+ class LabelList
5
+ def to_json
6
+ self.map(&:to_h).to_json
7
+ end
8
+ end
9
+ end
@@ -4,7 +4,7 @@ require_relative "label_list"
4
4
  class Ecu
5
5
  class CombinedList
6
6
  def self.from_lab(str)
7
- signals, labels, headers, subheaders = LabParser.call(str)
7
+ signals, labels, headers, subheaders = LabParser.new(str).call
8
8
  new(signals, labels, headers, subheaders)
9
9
  end
10
10
 
@@ -0,0 +1,57 @@
1
+ require "strscan"
2
+
3
+ class Ecu
4
+ class LabLexer
5
+ attr_reader :doc
6
+ def initialize(doc)
7
+ @doc = doc
8
+ @scan = StringScanner.new(doc)
9
+ end
10
+
11
+ KEYWORDS = [
12
+ "[SETTINGS]",
13
+ "[RAMCELL]",
14
+ "[Label]",
15
+ ].freeze
16
+
17
+ NEWLINE = %r{ \r\n|\n }x
18
+ SETTINGS_HEADER = %r{ \[SETTINGS\]\s*#{NEWLINE} }x
19
+ LABELS_HEADER = %r{ \[Label\]\s*#{NEWLINE} }x
20
+ SIGNALS_HEADER = %r{ \[RAMCELL\]\s*#{NEWLINE} }x
21
+ STRING = %r{ [^\r\n;]+ }x
22
+ WHITESPACE = %r{ [ \t]+ }x
23
+ SEPARATOR = %r{ [;] }x
24
+ COMMENT = %r{ ^[;].*$ }x
25
+
26
+ def next_token(state)
27
+ @scan.skip(WHITESPACE)
28
+
29
+ return if @scan.eos?
30
+
31
+ case
32
+ when @scan.skip(SETTINGS_HEADER) then :SETTINGS_HEADER
33
+ when @scan.skip(LABELS_HEADER) then :LABELS_HEADER
34
+ when @scan.skip("[LABEL]\n") then :LABELS_HEADER
35
+ when @scan.skip(SIGNALS_HEADER) then :SIGNALS_HEADER
36
+ when @scan.skip(STRING) then :STRING
37
+ when @scan.beginning_of_line? && @scan.skip(COMMENT) then :COMMENT
38
+ when @scan.skip(SEPARATOR) then :SEPARATOR
39
+ when @scan.skip(NEWLINE) then :NEWLINE
40
+ when @scan.skip(ANYTHING) then :ANYTHING
41
+
42
+ else
43
+ @scan.getch
44
+ :UNKNOWN_CHAR
45
+ end
46
+ end
47
+
48
+ def token_value
49
+ @doc.byteslice(@scan.pos - @scan.matched_size, @scan.matched_size)
50
+ end
51
+
52
+ def lineno
53
+ @doc.byteslice(0, @scan.pos).count("\n") +
54
+ (@scan.beginning_of_line? ? 0 : 1)
55
+ end
56
+ end
57
+ end
@@ -1,61 +1,132 @@
1
1
  require_relative "../../labels"
2
2
  require_relative "../../signals"
3
3
 
4
- class LabParser
5
- def self.call(str)
6
- lines = str.lines
7
-
8
- [
9
- extract_signals(block_bounded_by(lines, "[RAMCELL]", /\A\[/)),
10
- extract_labels(block_bounded_by(lines, "[Label]", /\A\[/)),
11
- extract_headers(block_bounded_by(lines, 0, "[SETTINGS]")),
12
- extract_headers(block_bounded_by(lines, "[SETTINGS]", /\A\[/)),
13
- ]
14
- end
4
+ require_relative "lab_lexer"
5
+ require_relative "lab_parser_error"
15
6
 
16
- def self.extract_labels(lines)
17
- Ecu::LabelList.new lines.
18
- reject { |l| l.blank? }.
19
- reject { |l| l.start_with?(";") }.
20
- map { |l| Ecu::Label.from_lab(l) }
21
- end
7
+ class Ecu
8
+ class LabParser
22
9
 
23
- def self.extract_signals(lines)
24
- Ecu::SignalList.new lines.
25
- reject { |l| l.blank? }.
26
- reject { |l| l.start_with?(";") }.
27
- map { |l| Ecu::Signal.from_lab(l) }
28
- end
10
+ attr_reader :lexer, :state, :labels, :signals, :headers, :subheaders
11
+ def initialize(doc)
12
+ @lexer = LabLexer.new(doc)
13
+ @state = :NO_SECTION
14
+ @signals = []
15
+ @labels = []
16
+ @headers = []
17
+ @subheaders = []
18
+ reset_entry!
19
+ end
29
20
 
30
- def self.extract_headers(lines)
31
- lines.
32
- select { |l| l.match(/\A; /) }.
33
- map { |l| l[2..-1].chomp }
34
- end
21
+ def reset_entry!
22
+ @properties = {}
23
+ end
35
24
 
36
- def self.block_bounded_by(lines, upper, lower)
37
- upper = case upper
38
- when Integer then upper
39
- when String
40
- return [] unless lines.any? { |l| l.strip.downcase == upper.downcase }
41
- lines.index { |l| l.strip.downcase == upper.downcase } + 1
42
- when Regexp
43
- return [] unless lines.any? { |l| l.match?(upper) }
44
- lines.index { |l| l.match?(upper) } + 1
25
+ def call
26
+ while tok = lexer.next_token(state)
27
+ begin
28
+ case state
29
+ in :NO_SECTION
30
+ case tok
31
+ in :SETTINGS_HEADER then next_state(:SETTINGS)
32
+ in :SIGNALS_HEADER then next_state(:SIGNALS)
33
+ in :LABELS_HEADER then next_state(:LABELS)
34
+ in :COMMENT then add_header(lexer.token_value)
35
+ in :NEWLINE then # noop
36
+ end
37
+ in :SETTINGS
38
+ case tok
39
+ in :SIGNALS_HEADER then next_state(:SIGNALS)
40
+ in :LABELS_HEADER then next_state(:LABELS)
41
+ in :COMMENT then add_subheader(lexer.token_value)
42
+ in :STRING then # save that shit?
43
+ in :SEPARATOR then # now comes the value
44
+ in :NEWLINE then # next setting
45
+ end
46
+ in :LABELS
47
+ case tok
48
+ in :SIGNALS_HEADER then next_state(:SIGNALS)
49
+ in :STRING then start_label(lexer.token_value)
50
+ in :COMMENT then # noop
51
+ in :NEWLINE then # noop
52
+ end
53
+ in :SIGNALS
54
+ case tok
55
+ in :LABELS_HEADER then next_state(:LABELS)
56
+ in :STRING then start_signal(lexer.token_value)
57
+ in :COMMENT then # noop
58
+ in :NEWLINE then # noop
59
+ end
60
+ in :LABEL
61
+ case tok
62
+ in :NEWLINE then labels << finish_label
63
+ in :SEPARATOR then # noop, sometimes used as end separator
45
64
  end
65
+ in :SIGNAL
66
+ case tok
67
+ in :STRING then add_property(lexer.token_value)
68
+ in :SEPARATOR then # noop, next property
69
+ in :NEWLINE then signals << finish_signal
70
+ end
71
+ end
72
+ rescue NoMatchingPatternError => e
73
+ raise LabParserError.new("Unexpected token #{debug_token(tok)} (state: #{state})", lexer)
74
+ rescue StandardError => e
75
+ raise LabParserError.new("#{e.message} (state: #{@state})", lexer)
76
+ end
77
+ end
78
+ [signals, labels, headers, subheaders]
79
+ end
46
80
 
47
- segment = lines[upper..-1]
81
+ def debug_token(tok)
82
+ "#{tok}: \"#{lexer.token_value}\" - #{@properties.inspect}"
83
+ end
48
84
 
49
- lower = case lower
50
- when Integer then lower
51
- when String
52
- return lines[upper..-1] unless segment.any? { |l| l.strip.downcase == lower.downcase }
53
- upper + segment.index { |l| l.strip.downcase == lower.downcase }
54
- when Regexp
55
- return lines[upper..-1] unless segment.any? { |l| l.match?(lower) }
56
- upper + segment.index { |l| l.match?(lower) }
57
- end
85
+ def next_state(newstate)
86
+ @state = newstate
87
+ yield if block_given?
88
+ end
89
+
90
+ def add_header(str)
91
+ @headers << str[1..].strip
92
+ end
93
+
94
+ def add_subheader(str)
95
+ @subheaders << str[1..].strip
96
+ end
97
+
98
+ def start_label(name)
99
+ next_state(:LABEL) do
100
+ @properties[:name] = name
101
+ end
102
+ end
103
+
104
+ def finish_label
105
+ Festwert.new(**@properties, value: nil).tap do
106
+ reset_entry!
107
+ next_state(:LABELS)
108
+ end
109
+ end
110
+
111
+ def start_signal(name)
112
+ next_state(:SIGNAL) do
113
+ @properties[:name] = name
114
+ end
115
+ end
116
+
117
+ def finish_signal
118
+ Signal.new(**@properties).tap do
119
+ reset_entry!
120
+ next_state(:SIGNALS)
121
+ end
122
+ end
58
123
 
59
- lines[upper...lower]
124
+ def add_property(str)
125
+ case @properties
126
+ in { task:, description: } then @properties[:description] << " " << str
127
+ in { task: } then @properties[:description] = str.strip
128
+ in { ** } then @properties[:task] = str.split("&").first
129
+ end
130
+ end
60
131
  end
61
132
  end
@@ -0,0 +1,38 @@
1
+ # TODO: This is exactly the same as the DcmParserError, maybe pull these
2
+ # together?
3
+
4
+ class Ecu
5
+ class LabParserError < StandardError
6
+
7
+ CTXLENGHT = 5
8
+
9
+ attr_reader :doc, :lineno
10
+ def initialize(msg, lexer)
11
+ @msg = msg
12
+ @doc = lexer.doc
13
+ @lineno = lexer.lineno
14
+ end
15
+
16
+ def message = @msg
17
+ def context
18
+ @doc
19
+ .lines[ctx_startline..ctx_endline]
20
+ .each
21
+ .with_index(ctx_startline + 1)
22
+ .map { |line, n| present(line, n, n == lineno) }
23
+ end
24
+
25
+ def present(line, n, highlight)
26
+ case highlight
27
+ when true then fmt(n) + " => | " + line
28
+ when false then fmt(n) + " | " + line
29
+ end
30
+ end
31
+
32
+ def ctx_startline = [0, lineno - CTXLENGHT].max
33
+ def ctx_endline = @endline ||= [doc.lines.count - 1, lineno + CTXLENGHT].min
34
+ def fmt_str = "%#{(ctx_endline + 1).to_s.length}d"
35
+ def fmt(n) = fmt_str % n
36
+
37
+ end
38
+ end
@@ -1,19 +1,5 @@
1
1
  class Ecu
2
2
  class Label
3
-
4
- # This is a silly hack, since the *.lab file doesn't contain any information
5
- # about the label in question (just the name). Subsequently, just initialize
6
- # a Festwert with no value and hope if does the job correctly
7
-
8
- def self.from_lab(line)
9
- name, description = line.strip.split(";")
10
- if description.nil?
11
- Festwert.new(name: name, value: nil)
12
- else
13
- Festwert.new(name: name, value: nil, description: description)
14
- end
15
- end
16
-
17
3
  def to_lab
18
4
  "#{name};#{description}"
19
5
  end
@@ -4,7 +4,7 @@ require_relative "lab_parser"
4
4
  class Ecu
5
5
  class LabelList
6
6
  def self.from_lab(str)
7
- _, labels, headers, subheaders = LabParser.call(str)
7
+ _, labels, headers, subheaders = LabParser.new(str).call
8
8
  new(labels, headers, subheaders)
9
9
  end
10
10
 
@@ -1,13 +1,5 @@
1
1
  class Ecu
2
2
  class Signal
3
- def self.from_lab(line)
4
- name, tasks, comment = line.strip.split(";")
5
- maintask = tasks.split("&").first rescue nil
6
- new(name: name,
7
- task: maintask,
8
- description: comment&.strip)
9
- end
10
-
11
3
  def to_lab
12
4
  "#{name};#{task};#{description}"
13
5
  end
@@ -4,7 +4,7 @@ require_relative "lab_parser"
4
4
  class Ecu
5
5
  class SignalList
6
6
  def self.from_lab(str)
7
- signals, _, headers, subheaders = LabParser.call(str)
7
+ signals, _, headers, subheaders = LabParser.new(str).call
8
8
  new(signals, headers, subheaders)
9
9
  end
10
10
 
@@ -10,6 +10,7 @@ require_relative "../interfaces/dcm/label_list"
10
10
  require_relative "../interfaces/lab/label_list"
11
11
  require_relative "../interfaces/a2l/label_list"
12
12
  require_relative "../interfaces/mfile/label_list"
13
+ require_relative "../interfaces/json/label_list"
13
14
 
14
15
  require "set"
15
16
  require_relative "../../core_ext"
data/lib/ecu/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Ecu
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: automotive-ecu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonas Mueller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-04 00:00:00.000000000 Z
11
+ date: 2025-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: terminal-table
@@ -97,6 +97,7 @@ files:
97
97
  - bin/pry
98
98
  - bin/rake
99
99
  - bin/rspec
100
+ - bin/sandbox
100
101
  - bin/setup
101
102
  - lib/core_ext.rb
102
103
  - lib/ecu.rb
@@ -119,8 +120,11 @@ files:
119
120
  - lib/ecu/interfaces/dcm/label.rb
120
121
  - lib/ecu/interfaces/dcm/label_list.rb
121
122
  - lib/ecu/interfaces/dcm/stuetzstellenverteilung.rb
123
+ - lib/ecu/interfaces/json/label_list.rb
122
124
  - lib/ecu/interfaces/lab/combined_list.rb
125
+ - lib/ecu/interfaces/lab/lab_lexer.rb
123
126
  - lib/ecu/interfaces/lab/lab_parser.rb
127
+ - lib/ecu/interfaces/lab/lab_parser_error.rb
124
128
  - lib/ecu/interfaces/lab/label.rb
125
129
  - lib/ecu/interfaces/lab/label_list.rb
126
130
  - lib/ecu/interfaces/lab/signal.rb