kicad 0.8.0 → 0.9.3

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: 7ea6528c30596177b5286780246f20331ff0a82109b527d0c311e5aca88d6b42
4
- data.tar.gz: b439278fbd1dd60ff5f931f67d7f6009517b179937b16cf0998de12beb3d8e1e
3
+ metadata.gz: 2a91d99c4c831dbc121717ef0609e05c8fe07c20d1b2044bfddda163c75c6620
4
+ data.tar.gz: 9110254e21b4e2913160100ec75f2887ea327107a0fefb257a23e77a9dfba362
5
5
  SHA512:
6
- metadata.gz: 57e43e2a123bb69b0497b7fd62cf66a54e5fcec279b3047dfdc7701bf8ae7016c33bfdc63fb298c28628a90a6e232b601e2eb0729495d8c4c8b4000dd9bb13f7
7
- data.tar.gz: 7a4474f86498962aa00c14aee3dc89a4a8c7d0d73aa66eee185cd526928b0286fd15a08762cd8604ce1fdcde651c65ed3348ba1a3e960c3bf5e034ca19d78871
6
+ metadata.gz: 44681ff57b45bebeea66e8a0dc92932e8219354dd2545e2dd4a9b4feb614aa7d2d528aff3946875c4373ff701e95015961e7865d0cc12a527404b84d29ae4b74
7
+ data.tar.gz: f2af698d30cd4b24a700a03a937f7a7a6c9913250c63aa99b3d75828191bb7d589d6fa06acd4a342f66c7d8a585d48ed8487aa0d1059e69a70262b2ce0da54cc
data/.gitignore CHANGED
@@ -1,3 +1,2 @@
1
1
  sample.*
2
2
  *.swp
3
- lib/kicad/grammar.rb
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kicad (0.8.0)
4
+ kicad (0.8.1)
5
5
  irb (~> 1.14, >= 1.14)
6
6
  treetop (~> 1.6, >= 1.6.9)
7
7
 
@@ -9,7 +9,6 @@ GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
11
  date (3.4.1)
12
- diff-lcs (1.6.1)
13
12
  io-console (0.8.0)
14
13
  irb (1.15.2)
15
14
  pp (>= 0.6.0)
@@ -27,19 +26,6 @@ GEM
27
26
  psych (>= 4.0.0)
28
27
  reline (0.6.1)
29
28
  io-console (~> 0.5)
30
- rspec (3.13.0)
31
- rspec-core (~> 3.13.0)
32
- rspec-expectations (~> 3.13.0)
33
- rspec-mocks (~> 3.13.0)
34
- rspec-core (3.13.3)
35
- rspec-support (~> 3.13.0)
36
- rspec-expectations (3.13.4)
37
- diff-lcs (>= 1.2.0, < 2.0)
38
- rspec-support (~> 3.13.0)
39
- rspec-mocks (3.13.3)
40
- diff-lcs (>= 1.2.0, < 2.0)
41
- rspec-support (~> 3.13.0)
42
- rspec-support (3.13.3)
43
29
  stringio (3.1.7)
44
30
  treetop (1.6.14)
45
31
  polyglot (~> 0.3)
@@ -51,8 +37,7 @@ PLATFORMS
51
37
  DEPENDENCIES
52
38
  bundler (>= 1.11)
53
39
  kicad!
54
- rake (>= 13)
55
- rspec (~> 3.3)
40
+ rake (~> 13)
56
41
 
57
42
  BUNDLED WITH
58
43
  2.6.2
data/README.md CHANGED
@@ -4,29 +4,31 @@ Parse, load, modify and rewrite Kicad (s-epression) files into a convenient tree
4
4
 
5
5
  ## Installation
6
6
 
7
- ```ruby
8
- gem 'kicad'
9
- ```
10
-
11
- or
12
-
13
7
  gem install kicad
14
8
 
15
9
  ## Usage
16
10
 
17
11
  $ irb -r kicad
18
- irb(main):001> k = KiCad.load("my_file.kicad_lib").value
19
- irb(main):001> k.children.filter{|c| c === KiCad::AST::Symbol}.map{|c| c.values[1]}
12
+ irb(main):001> k = KiCad.load("my_file.kicad_sym").value
13
+ irb(main):001> k.children.filter{|c| KiCad::AST::Symbol === c }.map{|c| c.values[1]}
20
14
  ["BC107", "CD4046"]
21
15
  irb(main):001> puts k.emit
22
16
  ...
23
17
 
24
18
  ## Development
25
19
 
26
- After checking out the repo, run `bundle` to install dependencies. Then, run `rake spec` to run the tests.
20
+ After checking out the repo, run `bundle` to install dependencies.
27
21
 
28
22
  To install this gem onto your local machine from local source code, run `rake install`.
29
23
 
24
+ ## Resources
25
+
26
+ KiCad uses a version of the Cadence SPECCTRA Design Language, defined in https://cdn.hackaday.io/files/1666717130852064/specctra.pdf
27
+
28
+ KiCad's documentation of this is at https://dev-docs.kicad.org/en/file-formats/sexpr-intro/
29
+
30
+ A related Rust library that was not consulted while building this is https://github.com/adom-inc/kicad_lib/tree/main/kicad_sexpr
31
+
30
32
  ## Contributing
31
33
 
32
34
  Bug reports and pull requests are welcome on GitHub at https://github.com/cjheath/kicad-rb
data/kicad.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  # spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "bundler", ">= 1.11"
24
24
  spec.add_development_dependency "rake", "~> 13"
25
25
 
26
26
  spec.add_runtime_dependency "treetop", ["~> 1.6", ">= 1.6.9"]
data/lib/kicad/ast.rb CHANGED
@@ -11,136 +11,390 @@ module KiCad
11
11
  def emit depth = 0
12
12
  "\t"*depth +
13
13
  '(' +
14
- @values.map{|v| value(v) }*' ' +
14
+ @values.map{|v| String === v ? v.inspect : v.to_s }*' ' +
15
15
  (@children.size == 0 ? '' : "\n" + @children.map{|c| c.emit(depth+1) }*''+"\t"*depth) +
16
16
  ")\n"
17
17
  end
18
18
 
19
- def value(v)
20
- case v
21
- when ::Symbol
22
- v.to_s
23
- when String
24
- v.inspect
25
- when Float, Integer
26
- v.to_s
27
- else
28
- "Internal error"
19
+ # Define setter and getter methods for each value type this class allows
20
+ def self.value_types vts
21
+ i = 1 # @values[0] is always the class symbol
22
+ vts.each do |k, v|
23
+ # puts "#{self.name} attribute #{k.to_s} => #{v.inspect}"
24
+ # puts "attr_accessor #{self.name}.#{k} (values #{v.inspect}) is stored in @values[#{i}]"
25
+ begin
26
+ o = i # Avoid capturing i after the loop ends
27
+ define_method(:"#{k}") do
28
+ # puts "accessing #{self.class.name}.#{k} as @values[#{o}]"
29
+ @values[o]
30
+ end
31
+ define_method(:"#{k}=") do |v|
32
+ # puts "setting #{self.class.name}.#{k} as @values[#{o}]"
33
+ # REVISIT: Check valid data type matching v
34
+ @values[o] = v
35
+ end
36
+ end
37
+ i = i+1
38
+ end
39
+ end
40
+
41
+ def children_of_type *cts # cts is an array of AST class symbols or strings
42
+ class_names = cts.flatten.map{|k| 'KiCad::AST::'+self.class.to_class_name(k)}
43
+ @children.filter{|h| class_names.include?(h.class.name) }
44
+ end
45
+
46
+ # Define methods for each child type this class allows
47
+ def self.child_types *cts
48
+ # puts "#{self.name} allows child types #{cts.inspect}"
49
+ cts.each do |c|
50
+ if Array === c
51
+ define_method(:"all_#{c[0].to_s}") do
52
+ # puts "Looking for all [#{class_names*', '}] in #{@children.map{|q| q.class.name}.inspect}"
53
+ children_of_type(c)
54
+ end
55
+ # REVISIT: Allow deleting and adding instances to the array
56
+ # new_child = KiCad.parse('(some new node)').value
57
+ # @children.append(new_child)
58
+ else
59
+ class_name = 'KiCad::AST::'+to_class_name(c)
60
+ define_method(:"#{c}") do
61
+ # puts "Looking for first #{class_name} in #{@children.map{|q| q.class.name}.inspect}"
62
+ a = children_of_type(c)
63
+ puts "Choosing first #{self.class.name}.#{c} of #{a.size}" if a.size > 1
64
+ a.first
65
+ end
66
+ # Allow deleting this instance
67
+ define_method(:"unset_#{c.to_s}") do
68
+ child = send(:"#{c}")
69
+ @children = @children - [child] if child
70
+ child ? true : nil
71
+ end
72
+ end
29
73
  end
30
74
  end
75
+
76
+ def self.to_class_name sym
77
+ sym.to_s.gsub(/\A[a-z]|_[a-z]/) {|from| from[-1].upcase }
78
+ end
79
+
80
+ def self.to_symbol class_name
81
+ class_name.to_s.gsub(/[A-Z]/) {|from| '_'+from[-1].downcase }.sub(/\A_/,'')
82
+ end
31
83
  end
32
84
 
33
85
  class KicadSymbolLib < Node
34
- def initialize values, children
35
- super
36
- end
86
+ child_types :version, :generator, :generator_version, [:symbol]
37
87
  end
38
88
 
39
- # Uncomment or add whatever class you need to customise:
89
+ class Generator < Node
90
+ end
40
91
 
41
- class Symbol < Node
92
+ class GeneratorVersion < Node
42
93
  end
43
94
 
44
- =begin
95
+ class Version < Node
96
+ end
97
+
98
+ # Uncomment or add whatever class you need to customise:
99
+
100
+ # Position Identifier
45
101
  class At < Node
102
+ value_types :x => Float, :y => Float, :angle => Float
46
103
  end
47
104
 
48
- class Center < Node
105
+ # Coordinate Point List
106
+ class Pts < Node
107
+ value_types({})
108
+ child_types [:xy]
49
109
  end
50
110
 
51
- class Circle < Node
111
+ class Xy < Node
112
+ value_types :x => Float, :y => Float
52
113
  end
53
114
 
54
- class Effects < Node
115
+ # Stroke Definition
116
+ class Stroke < Node
117
+ child_types :width, :type, :color
55
118
  end
56
119
 
57
- class EmbeddedFonts < Node
120
+ class Width < Node
121
+ value_types :width => Float
58
122
  end
59
123
 
60
- class End < Node
124
+ class Type < Node
125
+ value_types :type => [:dash, :dash_dot, :dash_dot_dot, :dot, :default, :solid]
61
126
  end
62
127
 
63
- class ExcludeFromSim < Node
128
+ class Color < Node
129
+ value_types :r => Integer, :g => Integer, :b => Integer, :a => Integer
64
130
  end
65
131
 
66
- class Fill < Node
132
+ # Text Effects
133
+ class Effects < Node
134
+ child_types :font, :justify, :hide
67
135
  end
68
136
 
69
137
  class Font < Node
138
+ child_types :face, :size, :thickness, :bold, :italic, :line_spacing
70
139
  end
71
140
 
72
- class Generator < Node
141
+ class Face < Node
142
+ value_types :face_name => String
73
143
  end
74
144
 
75
- class GeneratorVersion < Node
145
+ class Size < Node
146
+ value_types :height => Float, :width => Float
76
147
  end
77
148
 
78
- class Hide < Node
149
+ class Thickness < Node
150
+ value_types :thickness => Float
79
151
  end
80
152
 
81
- class InBom < Node
153
+ class Bold < Node
154
+ value_types :bold => [:no, :yes]
82
155
  end
83
156
 
84
- class Length < Node
157
+ class Italic < Node
158
+ value_types :bold => [:no, :yes]
85
159
  end
86
160
 
87
- class Name < Node
161
+ class LineSpacing < Node
162
+ value_types :line_spacing => Float
88
163
  end
89
164
 
90
- class Number < Node
165
+ class Justify < Node
166
+ value_types :justify => [[:right, :left, :top, :bottom, :mirror]] # Can have multiple
91
167
  end
92
168
 
93
- class Offset < Node
169
+ class Hide < Node
170
+ value_types :hide => [:no, :yes]
94
171
  end
95
172
 
96
- class OnBoard < Node
173
+ # Page Settings
174
+ class Paper < Node
175
+ # REVISIT: Either paper_size or width/height
176
+ value_types :paper_size => [:A0, :A1, :A2, :A3, :A4, :A5, :A, :B, :C, :D, :E],
177
+ :width => Float, :height => Float,
178
+ :portrait => [:portrait]
97
179
  end
98
180
 
99
- class Pin < Node
181
+ # Title Block
182
+ class TitleBlock < Node
183
+ child_types :title, :date, :rev, :company, # All take one string as value
184
+ [:comment] # N (1..9) and String # REVISIT: Implement Comment
100
185
  end
101
186
 
102
- class PinNames < Node
187
+ # Properties
188
+ class Property < Node
189
+ value_types :key => String, :value => String
190
+ child_types :at, :effects
191
+
192
+ # Set or clear (hide) on the property_node
193
+ def hide=(h = true)
194
+ v = (h ? :yes : :no)
195
+ if !effects
196
+ # puts "No effects yet"
197
+ prop = KiCad.parse(%Q{(effects(hide #{v}))})&.value
198
+ @children.append(prop) if prop
199
+ elsif (existing = effects.hide)
200
+ # puts "Effects and hide already"
201
+ existing.hide = v
202
+ else
203
+ # Create new (hide) node:
204
+ # puts "Effects but no hide"
205
+ prop = KiCad.parse(%Q{(hide #{v})})&.value
206
+ @children.append(prop) if prop
207
+ end
208
+ end
103
209
  end
104
210
 
105
- class PinNumbers < Node
211
+ # Universally Unique Identifier
212
+ class Uuid < Node
213
+ value_types :uuid => String
106
214
  end
107
215
 
108
- class Polyline < Node
216
+ # Images
217
+ class Image < Node
218
+ child_types :at, :scale, :layer, :uuid, :data
109
219
  end
110
220
 
111
- class Property < Node
221
+ class Data < Node
222
+ value_types :data => String # REVISIT: Base64 data - not encoded as a string I think?
112
223
  end
113
224
 
114
- class Pts < Node
225
+ class Circle < Node
226
+ child_types :center, :radius, :stroke, :fill
227
+ end
228
+
229
+ class Center < Xy
230
+ # value_types :x => Float, :y => Float
115
231
  end
116
232
 
117
233
  class Radius < Node
234
+ value_types :radius => Float
235
+ end
236
+
237
+ class Fill < Node
238
+ value_types :fill => [:no, :yes]
239
+ end
240
+
241
+ class Arc < Node
242
+ child_types :start, :mid, :end, :stroke, :fill
243
+ end
244
+
245
+ class Start < Xy
246
+ # value_types :x => Float, :y => Float
247
+ end
248
+
249
+ class Mid < Xy
250
+ # value_types :x => Float, :y => Float
251
+ end
252
+
253
+ class End < Xy
254
+ # value_types :x => Float, :y => Float
118
255
  end
119
256
 
120
257
  class Rectangle < Node
258
+ child_types :start, :end, :stroke, :fill
121
259
  end
122
260
 
123
- class Size < Node
261
+ class Polyline < Node
262
+ child_types :pts, :stroke, :fill
124
263
  end
125
264
 
126
- class Start < Node
265
+ class Text < Node
266
+ value_types :text => String
267
+ child_types :at, :effects
127
268
  end
128
269
 
129
- class Stroke < Node
270
+ class Offset < Xy
271
+ # value_types :x => Float, :y => Float
130
272
  end
131
273
 
132
- class Type < Node
274
+ # Symbol Pins
275
+ class Pin < Node
276
+ value_types :electrical_type => [
277
+ :input, # Pin is an input.
278
+ :output, # Pin is an output.
279
+ :bidirectional, # Pin can be both input and output.
280
+ :tri_state, # Pin is a tri-state output.
281
+ :passive, # Pin is electrically passive.
282
+ :free, # Not internally connected.
283
+ :unspecified, # Pin does not have a specified electrical type.
284
+ :power_in, # Pin is a power input.
285
+ :power_out, # Pin is a power output.
286
+ :open_collector, # Pin is an open collector output.
287
+ :open_emitter, # Pin is an open emitter output.
288
+ :no_connect, # Pin has no electrical connection.
289
+ ], :graphic_style => [
290
+ :line,
291
+ :inverted,
292
+ :clock,
293
+ :inverted_clock,
294
+ :input_low,
295
+ :clock_low,
296
+ :output_low,
297
+ :edge_clock_high,
298
+ :non_logic
299
+ ]
300
+ child_types :length, :name, :number
133
301
  end
134
302
 
135
- class Version < Node
303
+ class Length < Node
304
+ value_types :length => Float
136
305
  end
137
306
 
138
- class Width < Node
307
+ class Name < Node
308
+ value_types :name => String
309
+ child_types :effects
139
310
  end
140
311
 
141
- class Xy < Node
312
+ class Number < Node
313
+ value_types :number => String
314
+ child_types :effects
315
+ end
316
+
317
+ class Polygon < Node
318
+ child_types :pts, :stroke, :fill
319
+ end
320
+
321
+ # Symbols
322
+ class Symbol < Node
323
+ value_types :id => String
324
+ child_types :extends, :exclude_from_sym, :pin_numbers, :pin_names, :in_bom, :on_board,
325
+ [:property],
326
+ [:shape, :circle, :rectangle, :text, :polyline, :arc], # REVISIT: Lots more types of graphic items here...
327
+ [:pin],
328
+ [:symbol], # Child symbols (units) embedded in a parent
329
+ :unit_name,
330
+ :embedded_fonts
331
+
332
+ # Get or set Property values by key, or as a Hash
333
+ def property_node k
334
+ self.all_property.detect{|p| p.key == k}
335
+ end
336
+
337
+ def property k = nil
338
+ if k
339
+ property_node(k)&.value
340
+ else # Return all properties as a Hash
341
+ Hash[*self.all_property.map{|p| [p.key, p.value] }.flatten]
342
+ end
343
+ end
344
+
345
+ def [](k)
346
+ property[k]
347
+ end
348
+
349
+ def []=(k, v)
350
+ puts "Setting property #{k} to #{v.inspect}"
351
+ if (p = property_node)
352
+ p.send(:"#{self.class.to_symbol k}=", v)
353
+ else # Create new Property using the parser:
354
+ prop = KiCad.parse(%Q{
355
+ (property "#{k}" #{String === v ? v.inspect : v.to_s}
356
+ (at 0 0 0)
357
+ (effects(font(size 1.27 1.27)))
358
+ (hide yes)
359
+ )
360
+ })&.value
361
+ @children.append(prop) if prop
362
+ end
363
+ end
364
+ end
365
+
366
+ class Extends < Node
367
+ value_types :library_id => String
368
+ end
369
+
370
+ class PinNumbers < Node
371
+ value_types :hide => [:no, :yes]
372
+ end
373
+
374
+ class PinNames < Node
375
+ child_types :offset
376
+ value_types :hide => [:no, :yes]
377
+ end
378
+
379
+ class InBom < Node
380
+ value_types :in_bom => [:no, :yes]
381
+ end
382
+
383
+ class OnBoard < Node
384
+ value_types :on_board => [:no, :yes]
385
+ end
386
+
387
+ class UnitName < Node
388
+ value_types :name => String
389
+ end
390
+
391
+ class EmbeddedFonts < Node
392
+ value_types :embedded_fonts => [:no, :yes]
393
+ end
394
+
395
+ class ExcludeFromSim < Node
396
+ value_types :exclude_from_sim => [:no, :yes]
142
397
  end
143
- =end
144
398
 
145
399
  end
146
400
  end
@@ -0,0 +1,547 @@
1
+ # Autogenerated from a Treetop grammar. Edits may be lost.
2
+
3
+
4
+ require 'kicad/ast'
5
+
6
+ module KiCad
7
+ module SExpr
8
+ include Treetop::Runtime
9
+
10
+ def root
11
+ @root ||= :node
12
+ end
13
+
14
+ module Node0
15
+ def value
16
+ elements[0]
17
+ end
18
+
19
+ def s
20
+ elements[1]
21
+ end
22
+ end
23
+
24
+ module Node1
25
+ def node
26
+ elements[0]
27
+ end
28
+
29
+ def s
30
+ elements[1]
31
+ end
32
+ end
33
+
34
+ module Node2
35
+ def s1
36
+ elements[0]
37
+ end
38
+
39
+ def s2
40
+ elements[2]
41
+ end
42
+
43
+ def values
44
+ elements[3]
45
+ end
46
+
47
+ def nodes
48
+ elements[4]
49
+ end
50
+
51
+ def s3
52
+ elements[6]
53
+ end
54
+ end
55
+
56
+ module Node3
57
+ def value
58
+ klass_name = values.elements[0].value.value
59
+ klass = KiCad::AST::Node
60
+ if klass_name.is_a? ::Symbol # See if we have a defined class for this node type
61
+ klass_name = klass_name.to_s.gsub(/\A[a-z]|_[a-z]/) {|from| from[-1].upcase }
62
+ klass = KiCad::AST.const_get(klass_name, false) rescue KiCad::AST::Node
63
+ end
64
+ klass.new values.elements.map(&:value).map(&:value),
65
+ nodes.elements.map(&:node).map(&:value)
66
+ end
67
+ end
68
+
69
+ def _nt_node
70
+ start_index = index
71
+ if node_cache[:node].has_key?(index)
72
+ cached = node_cache[:node][index]
73
+ if cached
74
+ node_cache[:node][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
75
+ @index = cached.interval.end
76
+ end
77
+ return cached
78
+ end
79
+
80
+ i0, s0 = index, []
81
+ r1 = _nt_s
82
+ s0 << r1
83
+ if r1
84
+ if (match_len = has_terminal?('(', false, index))
85
+ r2 = true
86
+ @index += match_len
87
+ else
88
+ terminal_parse_failure('\'(\'')
89
+ r2 = nil
90
+ end
91
+ s0 << r2
92
+ if r2
93
+ r3 = _nt_s
94
+ s0 << r3
95
+ if r3
96
+ s4, i4 = [], index
97
+ loop do
98
+ i5, s5 = index, []
99
+ r6 = _nt_value
100
+ s5 << r6
101
+ if r6
102
+ r7 = _nt_s
103
+ s5 << r7
104
+ end
105
+ if s5.last
106
+ r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
107
+ r5.extend(Node0)
108
+ else
109
+ @index = i5
110
+ r5 = nil
111
+ end
112
+ if r5
113
+ s4 << r5
114
+ else
115
+ break
116
+ end
117
+ end
118
+ if s4.empty?
119
+ @index = i4
120
+ r4 = nil
121
+ else
122
+ r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
123
+ end
124
+ s0 << r4
125
+ if r4
126
+ s8, i8 = [], index
127
+ loop do
128
+ i9, s9 = index, []
129
+ r10 = _nt_node
130
+ s9 << r10
131
+ if r10
132
+ r11 = _nt_s
133
+ s9 << r11
134
+ end
135
+ if s9.last
136
+ r9 = instantiate_node(SyntaxNode,input, i9...index, s9)
137
+ r9.extend(Node1)
138
+ else
139
+ @index = i9
140
+ r9 = nil
141
+ end
142
+ if r9
143
+ s8 << r9
144
+ else
145
+ break
146
+ end
147
+ end
148
+ r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
149
+ s0 << r8
150
+ if r8
151
+ if (match_len = has_terminal?(')', false, index))
152
+ r12 = true
153
+ @index += match_len
154
+ else
155
+ terminal_parse_failure('\')\'')
156
+ r12 = nil
157
+ end
158
+ s0 << r12
159
+ if r12
160
+ r13 = _nt_s
161
+ s0 << r13
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ if s0.last
169
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
170
+ r0.extend(Node2)
171
+ r0.extend(Node3)
172
+ else
173
+ @index = i0
174
+ r0 = nil
175
+ end
176
+
177
+ node_cache[:node][start_index] = r0
178
+
179
+ r0
180
+ end
181
+
182
+ def _nt_value
183
+ start_index = index
184
+ if node_cache[:value].has_key?(index)
185
+ cached = node_cache[:value][index]
186
+ if cached
187
+ node_cache[:value][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
188
+ @index = cached.interval.end
189
+ end
190
+ return cached
191
+ end
192
+
193
+ i0 = index
194
+ r1 = _nt_string
195
+ if r1
196
+ r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
197
+ r0 = r1
198
+ else
199
+ r2 = _nt_number
200
+ if r2
201
+ r2 = SyntaxNode.new(input, (index-1)...index) if r2 == true
202
+ r0 = r2
203
+ else
204
+ r3 = _nt_symbol
205
+ if r3
206
+ r3 = SyntaxNode.new(input, (index-1)...index) if r3 == true
207
+ r0 = r3
208
+ else
209
+ @index = i0
210
+ r0 = nil
211
+ end
212
+ end
213
+ end
214
+
215
+ node_cache[:value][start_index] = r0
216
+
217
+ r0
218
+ end
219
+
220
+ module Symbol0
221
+ def value; :"#{text_value}"; end
222
+ end
223
+
224
+ def _nt_symbol
225
+ start_index = index
226
+ if node_cache[:symbol].has_key?(index)
227
+ cached = node_cache[:symbol][index]
228
+ if cached
229
+ node_cache[:symbol][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
230
+ @index = cached.interval.end
231
+ end
232
+ return cached
233
+ end
234
+
235
+ s0, i0 = [], index
236
+ loop do
237
+ if has_terminal?(@regexps[gr = '\A[a-zA-Z_]'] ||= Regexp.new(gr), :regexp, index)
238
+ r1 = true
239
+ @index += 1
240
+ else
241
+ terminal_parse_failure('[a-zA-Z_]')
242
+ r1 = nil
243
+ end
244
+ if r1
245
+ s0 << r1
246
+ else
247
+ break
248
+ end
249
+ end
250
+ if s0.empty?
251
+ @index = i0
252
+ r0 = nil
253
+ else
254
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
255
+ r0.extend(Symbol0)
256
+ r0.extend(Symbol0)
257
+ end
258
+
259
+ node_cache[:symbol][start_index] = r0
260
+
261
+ r0
262
+ end
263
+
264
+ module String0
265
+ end
266
+
267
+ module String1
268
+ def contents
269
+ elements[1]
270
+ end
271
+
272
+ end
273
+
274
+ module String2
275
+ def value; contents.text_value.force_encoding(Encoding::UTF_8); end
276
+ end
277
+
278
+ def _nt_string
279
+ start_index = index
280
+ if node_cache[:string].has_key?(index)
281
+ cached = node_cache[:string][index]
282
+ if cached
283
+ node_cache[:string][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
284
+ @index = cached.interval.end
285
+ end
286
+ return cached
287
+ end
288
+
289
+ i0, s0 = index, []
290
+ if (match_len = has_terminal?('"', false, index))
291
+ r1 = true
292
+ @index += match_len
293
+ else
294
+ terminal_parse_failure('\'"\'')
295
+ r1 = nil
296
+ end
297
+ s0 << r1
298
+ if r1
299
+ s2, i2 = [], index
300
+ loop do
301
+ i3 = index
302
+ if (match_len = has_terminal?('\\"', false, index))
303
+ r4 = instantiate_node(SyntaxNode,input, index...(index + match_len))
304
+ @index += match_len
305
+ else
306
+ terminal_parse_failure('\'\\\\"\'')
307
+ r4 = nil
308
+ end
309
+ if r4
310
+ r4 = SyntaxNode.new(input, (index-1)...index) if r4 == true
311
+ r3 = r4
312
+ else
313
+ i5, s5 = index, []
314
+ i6 = index
315
+ if (match_len = has_terminal?('"', false, index))
316
+ r7 = true
317
+ @index += match_len
318
+ else
319
+ terminal_parse_failure('\'"\'')
320
+ r7 = nil
321
+ end
322
+ if r7
323
+ @index = i6
324
+ r6 = nil
325
+ terminal_parse_failure('\'"\'', true)
326
+ else
327
+ @terminal_failures.pop
328
+ @index = i6
329
+ r6 = instantiate_node(SyntaxNode,input, index...index)
330
+ end
331
+ s5 << r6
332
+ if r6
333
+ if index < input_length
334
+ r8 = true
335
+ @index += 1
336
+ else
337
+ terminal_parse_failure("any character")
338
+ r8 = nil
339
+ end
340
+ s5 << r8
341
+ end
342
+ if s5.last
343
+ r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
344
+ r5.extend(String0)
345
+ else
346
+ @index = i5
347
+ r5 = nil
348
+ end
349
+ if r5
350
+ r5 = SyntaxNode.new(input, (index-1)...index) if r5 == true
351
+ r3 = r5
352
+ else
353
+ @index = i3
354
+ r3 = nil
355
+ end
356
+ end
357
+ if r3
358
+ s2 << r3
359
+ else
360
+ break
361
+ end
362
+ end
363
+ r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
364
+ s0 << r2
365
+ if r2
366
+ if (match_len = has_terminal?('"', false, index))
367
+ r9 = true
368
+ @index += match_len
369
+ else
370
+ terminal_parse_failure('\'"\'')
371
+ r9 = nil
372
+ end
373
+ s0 << r9
374
+ end
375
+ end
376
+ if s0.last
377
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
378
+ r0.extend(String1)
379
+ r0.extend(String2)
380
+ else
381
+ @index = i0
382
+ r0 = nil
383
+ end
384
+
385
+ node_cache[:string][start_index] = r0
386
+
387
+ r0
388
+ end
389
+
390
+ module Number0
391
+ end
392
+
393
+ module Number1
394
+ end
395
+
396
+ module Number2
397
+ def value; eval(text_value); end
398
+ end
399
+
400
+ def _nt_number
401
+ start_index = index
402
+ if node_cache[:number].has_key?(index)
403
+ cached = node_cache[:number][index]
404
+ if cached
405
+ node_cache[:number][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
406
+ @index = cached.interval.end
407
+ end
408
+ return cached
409
+ end
410
+
411
+ i0, s0 = index, []
412
+ if (match_len = has_terminal?('-', false, index))
413
+ r2 = true
414
+ @index += match_len
415
+ else
416
+ terminal_parse_failure('\'-\'')
417
+ r2 = nil
418
+ end
419
+ if r2
420
+ r1 = r2
421
+ else
422
+ r1 = instantiate_node(SyntaxNode,input, index...index)
423
+ end
424
+ s0 << r1
425
+ if r1
426
+ s3, i3 = [], index
427
+ loop do
428
+ if has_terminal?(@regexps[gr = '\A[0-9]'] ||= Regexp.new(gr), :regexp, index)
429
+ r4 = true
430
+ @index += 1
431
+ else
432
+ terminal_parse_failure('[0-9]')
433
+ r4 = nil
434
+ end
435
+ if r4
436
+ s3 << r4
437
+ else
438
+ break
439
+ end
440
+ end
441
+ if s3.empty?
442
+ @index = i3
443
+ r3 = nil
444
+ else
445
+ r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
446
+ end
447
+ s0 << r3
448
+ if r3
449
+ i6, s6 = index, []
450
+ if (match_len = has_terminal?('.', false, index))
451
+ r7 = true
452
+ @index += match_len
453
+ else
454
+ terminal_parse_failure('\'.\'')
455
+ r7 = nil
456
+ end
457
+ s6 << r7
458
+ if r7
459
+ s8, i8 = [], index
460
+ loop do
461
+ if has_terminal?(@regexps[gr = '\A[0-9]'] ||= Regexp.new(gr), :regexp, index)
462
+ r9 = true
463
+ @index += 1
464
+ else
465
+ terminal_parse_failure('[0-9]')
466
+ r9 = nil
467
+ end
468
+ if r9
469
+ s8 << r9
470
+ else
471
+ break
472
+ end
473
+ end
474
+ r8 = instantiate_node(SyntaxNode,input, i8...index, s8)
475
+ s6 << r8
476
+ end
477
+ if s6.last
478
+ r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
479
+ r6.extend(Number0)
480
+ else
481
+ @index = i6
482
+ r6 = nil
483
+ end
484
+ if r6
485
+ r5 = r6
486
+ else
487
+ r5 = instantiate_node(SyntaxNode,input, index...index)
488
+ end
489
+ s0 << r5
490
+ end
491
+ end
492
+ if s0.last
493
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
494
+ r0.extend(Number1)
495
+ r0.extend(Number2)
496
+ else
497
+ @index = i0
498
+ r0 = nil
499
+ end
500
+
501
+ node_cache[:number][start_index] = r0
502
+
503
+ r0
504
+ end
505
+
506
+ def _nt_s
507
+ start_index = index
508
+ if node_cache[:s].has_key?(index)
509
+ cached = node_cache[:s][index]
510
+ if cached
511
+ node_cache[:s][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
512
+ @index = cached.interval.end
513
+ end
514
+ return cached
515
+ end
516
+
517
+ s0, i0 = [], index
518
+ loop do
519
+ if has_terminal?(@regexps[gr = '\A[ \\t\\r\\n]'] ||= Regexp.new(gr), :regexp, index)
520
+ r1 = true
521
+ @index += 1
522
+ else
523
+ terminal_parse_failure('[ \\t\\r\\n]')
524
+ r1 = nil
525
+ end
526
+ if r1
527
+ s0 << r1
528
+ else
529
+ break
530
+ end
531
+ end
532
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
533
+
534
+ node_cache[:s][start_index] = r0
535
+
536
+ r0
537
+ end
538
+
539
+
540
+ class Parser < Treetop::Runtime::CompiledParser
541
+ include SExpr
542
+ end
543
+ end
544
+
545
+ SExprParser = SExpr::Parser
546
+
547
+ end
data/lib/kicad/grammar.tt CHANGED
@@ -3,13 +3,12 @@ require 'kicad/ast'
3
3
  module KiCad
4
4
  grammar SExpr
5
5
  rule node
6
- '(' s values:( value s)+ nodes:(node s)* ')' s
6
+ s '(' s values:( value s)+ nodes:(node s)* ')' s
7
7
  { def value
8
8
  klass_name = values.elements[0].value.value
9
9
  klass = KiCad::AST::Node
10
- if klass_name.is_a? ::Symbol
11
- # See if we have a defined class for this node type
12
- klass_name = klass_name.to_s.gsub(/\A[a-z]|_[a-z]/) {|from| from[-1].upcase }
10
+ if klass_name.is_a? ::Symbol # See if we have a defined class for this node type
11
+ klass_name = klass_name.to_s.gsub(/\A[a-z]|_[a-z]/) {|from| from[-1].upcase }
13
12
  klass = KiCad::AST.const_get(klass_name, false) rescue KiCad::AST::Node
14
13
  end
15
14
  klass.new values.elements.map(&:value).map(&:value),
@@ -28,8 +27,8 @@ module KiCad
28
27
  end
29
28
 
30
29
  rule string
31
- '"' ('\\"' / !'"' .)* '"'
32
- { def value; eval(text_value); end } # REVISIT: Risk of evaluating code
30
+ '"' contents:( ('\\"' / !'"' .)* ) '"'
31
+ { def value; contents.text_value.force_encoding(Encoding::UTF_8); end }
33
32
  end
34
33
 
35
34
  rule number
data/lib/kicad/parser.rb CHANGED
@@ -12,7 +12,7 @@ module KiCad
12
12
  end
13
13
 
14
14
  def self.load filename
15
- self.parse File.read(filename)
15
+ self.parse File.read(filename, :encoding => 'iso-8859-1')
16
16
  end
17
17
 
18
18
  class Parser < KiCad::SExprParser
data/lib/kicad/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module KiCad
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.3"
3
3
  end
metadata CHANGED
@@ -1,26 +1,26 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kicad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clifford Heath
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-05 00:00:00.000000000 Z
10
+ date: 2025-05-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '1.11'
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - "~>"
23
+ - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.11'
26
26
  - !ruby/object:Gem::Dependency
@@ -92,6 +92,7 @@ files:
92
92
  - kicad.gemspec
93
93
  - lib/kicad.rb
94
94
  - lib/kicad/ast.rb
95
+ - lib/kicad/grammar.rb
95
96
  - lib/kicad/grammar.tt
96
97
  - lib/kicad/parser.rb
97
98
  - lib/kicad/version.rb