unitsml 0.6.9 → 0.6.10

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.
@@ -72,8 +72,13 @@ module Unitsml
72
72
  end
73
73
  end
74
74
 
75
+ # Keys sorted so the output is byte-identical regardless of the
76
+ # hash insertion order the unitsdb/lutaml load path happens to
77
+ # produce on a given Ruby or gem version.
75
78
  def serialize_hash(value)
76
- "{#{value.map { |k, v| "#{serialize(k)}=>#{serialize(v)}" }.join(',')}}"
79
+ "{#{value.sort_by do |k, _|
80
+ k.to_s
81
+ end.map { |k, v| "#{serialize(k)}=>#{serialize(v)}" }.join(',')}}"
77
82
  end
78
83
 
79
84
  def serialize_array(value)
data/lib/unitsml/opal.rb CHANGED
@@ -36,6 +36,7 @@ require "unitsml/errors/base_error"
36
36
  require "unitsml/errors/invalid_model_error"
37
37
  require "unitsml/errors/opal_payload_not_bundled_error"
38
38
  require "unitsml/errors/plurimath_load_error"
39
+ require "unitsml/errors/parg_load_error"
39
40
  require "unitsml/errors/unsupported_payload_type_error"
40
41
 
41
42
  require "unitsml/opal/database_payload"
@@ -87,15 +88,13 @@ require "unitsml/extender"
87
88
  require "unitsml/fenced"
88
89
  require "unitsml/fenced_numeric"
89
90
  require "unitsml/formula"
90
- require "unitsml/intermediate_exp_rules"
91
91
  require "unitsml/mathml_helper"
92
92
  require "unitsml/number"
93
- require "unitsml/parse"
93
+ require "unitsml/parg"
94
94
  require "unitsml/parser"
95
95
  require "unitsml/prefix"
96
96
  require "unitsml/prefix_adapter"
97
97
  require "unitsml/sqrt"
98
- require "unitsml/transform"
99
98
  require "unitsml/unit"
100
99
  require "unitsml/utility"
101
100
  require "unitsml/xml"
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module Parg
5
+ # Maps asciiunits artifact parse trees (capture-named hashes whose leaves
6
+ # are {value, offset, ...} slices) onto the same object model the Parslet
7
+ # Transform produces. Dispatch is on the exact key set of each node
8
+ # (sorted), bottom-up, mirroring rule-for-rule the Transform rules in
9
+ # unitsml/transform.rb.
10
+ class TreeMapper
11
+ def apply(tree)
12
+ map(normalize(tree))
13
+ end
14
+
15
+ private
16
+
17
+ # The artifact runtime hands back symbol-keyed hashes with parsanol
18
+ # Slice leaves; the mapper works on string keys and stringified leaves.
19
+ def normalize(node)
20
+ case node
21
+ when Hash then node.to_h { |key, value| [key.to_s, normalize(value)] }
22
+ when Array then node.map { |value| normalize(value) }
23
+ else node
24
+ end
25
+ end
26
+
27
+ # The case below is a dispatch table mirroring unitsml/transform.rb
28
+ # rule-for-rule; splitting it would obscure the 1:1 parity contract.
29
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
30
+ def map(node)
31
+ return node unless node.is_a?(Hash)
32
+
33
+ node = node.transform_values do |value|
34
+ value.is_a?(Hash) ? map(value) : value
35
+ end
36
+ case node.keys.map(&:to_s).sort
37
+ when %w[dimension] then Dimension.new(text(node["dimension"]))
38
+ when %w[dimension extender power_numerator sequence]
39
+ Formula.new(
40
+ [Dimension.new(text(node["dimension"]), node["power_numerator"]),
41
+ Extender.new(text(node["extender"])),
42
+ node["sequence"]],
43
+ )
44
+ when %w[dimension extender sequence]
45
+ Formula.new(
46
+ [Dimension.new(text(node["dimension"])),
47
+ Extender.new(text(node["extender"])),
48
+ node["sequence"]],
49
+ )
50
+ when %w[dimension power_numerator]
51
+ Dimension.new(text(node["dimension"]), node["power_numerator"])
52
+ when %w[extender first_set second_set]
53
+ Formula.new(
54
+ [node["first_set"], Extender.new(text(node["extender"])),
55
+ node["second_set"]],
56
+ )
57
+ when %w[extender implicit_extended sequence]
58
+ Formula.new(
59
+ [node["implicit_extended"], Extender.new(text(node["extender"])),
60
+ node["sequence"]],
61
+ )
62
+ when %w[extender int_exp sequence]
63
+ Formula.new(
64
+ [node["int_exp"], Extender.new(text(node["extender"])),
65
+ node["sequence"]],
66
+ )
67
+ when %w[extender power_numerator sequence unit]
68
+ Formula.new(
69
+ [Unit.new(text(node["unit"]), node["power_numerator"]),
70
+ Extender.new(text(node["extender"])),
71
+ node["sequence"]],
72
+ )
73
+ when %w[extender power_numerator prefix sequence unit]
74
+ Formula.new(
75
+ [Unit.new(text(node["unit"]), node["power_numerator"],
76
+ prefix: Prefix.new(text(node["prefix"]))),
77
+ Extender.new(text(node["extender"])),
78
+ node["sequence"]],
79
+ )
80
+ when %w[extender prefix sequence unit]
81
+ Formula.new(
82
+ [Unit.new(text(node["unit"]),
83
+ prefix: Prefix.new(text(node["prefix"]))),
84
+ Extender.new(text(node["extender"])),
85
+ node["sequence"]],
86
+ )
87
+ when %w[extender sequence unit]
88
+ Formula.new(
89
+ [Unit.new(text(node["unit"])),
90
+ Extender.new(text(node["extender"])),
91
+ node["sequence"]],
92
+ )
93
+ when %w[first_int_exp_set second_int_exp_set]
94
+ Formula.new(
95
+ [set_to_fence(node["first_int_exp_set"]),
96
+ set_to_fence(node["second_int_exp_set"])],
97
+ )
98
+ when %w[first_int_exp_set second_set]
99
+ Formula.new(
100
+ [set_to_fence(node["first_int_exp_set"]), node["second_set"]],
101
+ )
102
+ when %w[first_set second_int_exp_set]
103
+ Formula.new(
104
+ [node["first_set"], set_to_fence(node["second_int_exp_set"])],
105
+ )
106
+ when %w[first_set second_set]
107
+ Formula.new([node["first_set"], node["second_set"]])
108
+ when %w[implicit_extended] then node["implicit_extended"]
109
+ when %w[int_exp] then node["int_exp"]
110
+ when %w[int_exp sequence]
111
+ Formula.new([node["int_exp"], node["sequence"]])
112
+ when %w[integer] then Number.new(text(node["integer"]))
113
+ when %w[close_paren integer open_paren]
114
+ Fenced.new(text(node["open_paren"]),
115
+ Number.new(text(node["integer"])),
116
+ text(node["close_paren"]))
117
+ when %w[close_paren int_exp open_paren]
118
+ Fenced.new(text(node["open_paren"]), node["int_exp"],
119
+ text(node["close_paren"]))
120
+ when %w[power_numerator unit]
121
+ Unit.new(text(node["unit"]), node["power_numerator"])
122
+ when %w[power_numerator prefix unit]
123
+ Unit.new(text(node["unit"]), node["power_numerator"],
124
+ prefix: Prefix.new(text(node["prefix"])))
125
+ when %w[prefix] then Prefix.new(text(node["prefix"]))
126
+ when %w[prefix unit]
127
+ Unit.new(text(node["unit"]), prefix: Prefix.new(text(node["prefix"])))
128
+ when %w[sqrt] then Sqrt.new(node["sqrt"])
129
+ when %w[unit] then Unit.new(text(node["unit"]))
130
+ else
131
+ # The Parslet Transform leaves unmatched key combinations as raw
132
+ # hashes with symbol keys (e.g. {sqrt:, sequence:}); mirror that.
133
+ # Terminal slices (identifiable by their "value" key) keep their
134
+ # string keys.
135
+ node.key?("value") ? node : node.transform_keys(&:to_sym)
136
+ end
137
+ end
138
+
139
+ def text(slice)
140
+ slice.is_a?(Hash) ? slice.fetch("value") : slice.to_s
141
+ end
142
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
143
+
144
+ def set_to_fence(set)
145
+ return set if set.is_a?(Fenced)
146
+
147
+ Fenced.new("(", set, ")")
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module Parg
5
+ autoload :TreeMapper, "unitsml/parg/tree_mapper"
6
+ end
7
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unitsml
4
+ # Parses AsciiUnits expressions through the asciiunits PARG artifact
5
+ # (github.com/unitsml/asciiunits) into the Formula object model.
4
6
  class Parser
5
7
  attr_accessor :text
6
8
 
@@ -13,21 +15,42 @@ module Unitsml
13
15
  end
14
16
 
15
17
  def parse
16
- nodes = Parse.new.parse(text)
17
- transformed = Transform.new.apply(nodes)
18
- formula_value = transformed.is_a?(Formula) ? transformed.value : [transformed].flatten
19
- formula = Formula.new(
20
- formula_value,
21
- explicit_value: @extras_hash,
22
- root: true,
23
- orig_text: @orig_text,
24
- norm_text: text,
25
- )
18
+ tree = self.class.artifact.parse("expression", text)
19
+ mapped = Parg::TreeMapper.new.apply(tree)
20
+ formula = build_formula(mapped)
26
21
  update_units_exponents(formula.value, false)
27
22
  formula.value.first.only_instance = true if text.end_with?("-")
28
23
  formula
29
24
  end
30
25
 
26
+ class << self
27
+ def artifact
28
+ @artifact ||= load_artifact
29
+ end
30
+
31
+ private
32
+
33
+ def load_artifact
34
+ if RUBY_ENGINE == "opal"
35
+ raise Errors::PargLoadError.new(
36
+ "the asciiunits gem",
37
+ "the parsanol runtime is not available under Opal; " \
38
+ "use a runtime with native code access",
39
+ )
40
+ end
41
+
42
+ # Opal's builder statically scans literal requires; neither
43
+ # asciiunits nor parsanol has an Opal build, so keep the requires
44
+ # dynamic (this line never runs under Opal — the RUBY_ENGINE guard
45
+ # above raises first).
46
+ data_gem = "asciiunits"
47
+ require data_gem
48
+ Asciiunits.load_artifact
49
+ rescue LoadError => e
50
+ raise Errors::PargLoadError.new("the asciiunits gem", e.message)
51
+ end
52
+ end
53
+
31
54
  def update_units_exponents(array, inverse, sqrt = false)
32
55
  array.each do |object|
33
56
  if object.is_a?(Sqrt)
@@ -71,6 +94,17 @@ module Unitsml
71
94
 
72
95
  private
73
96
 
97
+ def build_formula(mapped)
98
+ formula_value = mapped.is_a?(Formula) ? mapped.value : [mapped].flatten
99
+ Formula.new(
100
+ formula_value,
101
+ explicit_value: @extras_hash,
102
+ root: true,
103
+ orig_text: @orig_text,
104
+ norm_text: text,
105
+ )
106
+ end
107
+
74
108
  def extract_equation(text)
75
109
  return text unless text&.start_with?("unitsml(")
76
110
 
@@ -19,6 +19,12 @@ module Unitsml
19
19
  end
20
20
  end
21
21
 
22
+ def prefixed_symbols
23
+ @prefixed_symbols ||= symbols_ids.select do |unit|
24
+ find_by_symbol_id(unit).prefixed && !%r{\*|\^|/|^1$}.match?(unit)
25
+ end
26
+ end
27
+
22
28
  def find_by_symbol_id(sym_id)
23
29
  symbols_hash[sym_id]
24
30
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unitsml
4
- VERSION = "0.6.9"
4
+ VERSION = "0.6.10"
5
5
  end
data/lib/unitsml.rb CHANGED
@@ -13,17 +13,15 @@ module Unitsml
13
13
  autoload :Fenced, "unitsml/fenced"
14
14
  autoload :FencedNumeric, "unitsml/fenced_numeric"
15
15
  autoload :Formula, "unitsml/formula"
16
- autoload :IntermediateExpRules, "unitsml/intermediate_exp_rules"
17
16
  autoload :MathmlHelper, "unitsml/mathml_helper"
18
17
  autoload :Model, "unitsml/model"
19
18
  autoload :Namespace, "unitsml/namespace"
20
19
  autoload :Number, "unitsml/number"
21
- autoload :Parse, "unitsml/parse"
20
+ autoload :Parg, "unitsml/parg"
22
21
  autoload :Parser, "unitsml/parser"
23
22
  autoload :Prefix, "unitsml/prefix"
24
23
  autoload :PrefixAdapter, "unitsml/prefix_adapter"
25
24
  autoload :Sqrt, "unitsml/sqrt"
26
- autoload :Transform, "unitsml/transform"
27
25
  autoload :Unit, "unitsml/unit"
28
26
  autoload :Unitsdb, "unitsml/unitsdb"
29
27
  autoload :Utility, "unitsml/utility"
@@ -31,6 +29,6 @@ module Unitsml
31
29
  autoload :Xml, "unitsml/xml"
32
30
 
33
31
  def parse(string)
34
- Unitsml::Parser.new(string).parse
32
+ Parser.new(string).parse
35
33
  end
36
34
  end
data/unitsml.gemspec CHANGED
@@ -32,10 +32,9 @@ Gem::Specification.new do |spec|
32
32
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
33
  spec.require_paths = ["lib"]
34
34
 
35
+ spec.add_dependency "asciiunits", "~> 0.1"
35
36
  spec.add_dependency "htmlentities"
36
37
  spec.add_dependency "lutaml-model", "~> 0.8.0"
37
38
  spec.add_dependency "mml", "~> 2.4.0"
38
- spec.add_dependency "ox"
39
- spec.add_dependency "parslet"
40
39
  spec.add_dependency "unitsdb", "~> 2.2.5"
41
40
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unitsml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9
4
+ version: 0.6.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-17 00:00:00.000000000 Z
11
+ date: 2026-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: asciiunits
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: htmlentities
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,34 +66,6 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: 2.4.0
55
- - !ruby/object:Gem::Dependency
56
- name: ox
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: parslet
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: unitsdb
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -127,13 +113,13 @@ files:
127
113
  - lib/unitsml/errors/base_error.rb
128
114
  - lib/unitsml/errors/invalid_model_error.rb
129
115
  - lib/unitsml/errors/opal_payload_not_bundled_error.rb
116
+ - lib/unitsml/errors/parg_load_error.rb
130
117
  - lib/unitsml/errors/plurimath_load_error.rb
131
118
  - lib/unitsml/errors/unsupported_payload_type_error.rb
132
119
  - lib/unitsml/extender.rb
133
120
  - lib/unitsml/fenced.rb
134
121
  - lib/unitsml/fenced_numeric.rb
135
122
  - lib/unitsml/formula.rb
136
- - lib/unitsml/intermediate_exp_rules.rb
137
123
  - lib/unitsml/mathml_helper.rb
138
124
  - lib/unitsml/model.rb
139
125
  - lib/unitsml/model/dimension.rb
@@ -166,12 +152,12 @@ files:
166
152
  - lib/unitsml/opal.rb
167
153
  - lib/unitsml/opal/database_payload.rb
168
154
  - lib/unitsml/opal/payload_generator.rb
169
- - lib/unitsml/parse.rb
155
+ - lib/unitsml/parg.rb
156
+ - lib/unitsml/parg/tree_mapper.rb
170
157
  - lib/unitsml/parser.rb
171
158
  - lib/unitsml/prefix.rb
172
159
  - lib/unitsml/prefix_adapter.rb
173
160
  - lib/unitsml/sqrt.rb
174
- - lib/unitsml/transform.rb
175
161
  - lib/unitsml/unit.rb
176
162
  - lib/unitsml/unitsdb.rb
177
163
  - lib/unitsml/unitsdb/database.rb
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Unitsml
4
- module IntermediateExpRules
5
- include Parslet
6
-
7
- # Rules for slashed number
8
- rule(:slashed_number_int_exp) do
9
- slashed_number | (opening_paren >> slashed_number_named_int_exp.as(:int_exp) >> closing_paren)
10
- end
11
- rule(:slashed_number_named_int_exp) do
12
- (opening_paren.as(:open_paren) >> slashed_number_int_exp >> closing_paren.as(:close_paren)) |
13
- slashed_number_int_exp
14
- end
15
-
16
- # Rules for prefixes_units
17
- rule(:wrapper_prefixes_units_value) do
18
- int_exp_prefixes_units | prefixes_units_int_exp
19
- end
20
-
21
- rule(:extended_prefixed_units) do
22
- extender >> spaces? >> prefixes_units_int_exp.as(:sequence)
23
- end
24
-
25
- rule(:int_exp_prefixes_units) do
26
- opening_paren >> spaces? >> named_int_exp_prefixes_units.as(:int_exp) >> spaces? >> closing_paren
27
- end
28
-
29
- rule(:implicit_extended) do
30
- (prefixes_units.as(:first_set) >> spaces.as(:extender) >> prefixes_units.as(:second_set)) |
31
- (int_exp_prefixes_units.as(:first_int_exp_set) >> spaces? >> int_exp_prefixes_units.as(:second_int_exp_set)) |
32
- (prefixes_units.as(:first_set) >> spaces? >> int_exp_prefixes_units.as(:second_int_exp_set)) |
33
- (int_exp_prefixes_units.as(:first_int_exp_set) >> spaces? >> prefixes_units.as(:second_set))
34
- end
35
-
36
- rule(:prefixes_units_int_exp) do
37
- (implicit_extended.as(:implicit_extended) >> spaces? >> prefixes_units_int_exp.as(:sequence).maybe) |
38
- (implicit_extended.as(:implicit_extended) >> spaces? >> extender >> spaces? >> prefixes_units_int_exp.as(:sequence).maybe) |
39
- (int_exp_prefixes_units >> spaces? >> extender >> spaces? >> prefixes_units_int_exp.as(:sequence).maybe) |
40
- (prefixes_units >> spaces? >> extender >> spaces? >> prefixes_units_int_exp.as(:sequence).maybe) |
41
- (int_exp_prefixes_units >> prefixes_units_int_exp.as(:sequence).maybe) |
42
- (prefixes_units >> prefixes_units_int_exp.as(:sequence).maybe)
43
- end
44
-
45
- rule(:prefixes_units_named_int_exp) do
46
- opening_paren.as(:open_paren) >> spaces? >> wrapper_prefixes_units_value.as(:int_exp) >> spaces? >> closing_paren.as(:close_paren)
47
- end
48
-
49
- rule(:named_int_exp_prefixes_units) do
50
- (prefixes_units_named_int_exp.as(:int_exp) >> (prefixes_units_int_exp | extended_prefixed_units).maybe) |
51
- prefixes_units_int_exp
52
- end
53
-
54
- # Rules for dimension_rules
55
- rule(:dimension_rules_int_exp) do
56
- (dimension_rules.as(:first_set) >> spaces.as(:extender) >> dimension_rules.as(:second_set) >> dimension_rules_int_exp.maybe) |
57
- (dimension_rules.as(:first_set) >> spaces? >> int_exp_dimension_rules.as(:second_int_exp_set) >> dimension_rules_int_exp.maybe) |
58
- (int_exp_dimension_rules.as(:first_int_exp_set) >> spaces? >> dimension_rules.as(:second_set) >> dimension_rules_int_exp.maybe) |
59
- (int_exp_dimension_rules.as(:first_int_exp_set) >> spaces? >> int_exp_dimension_rules.as(:second_int_exp_set) >> dimension_rules_int_exp.maybe) |
60
- int_exp_dimension_rules |
61
- dimension_rules
62
- end
63
-
64
- rule(:int_exp_dimension_rules) do
65
- opening_paren >> dimension_rules_named_exp.as(:int_exp) >> closing_paren >> extended_dimension_rules.maybe
66
- end
67
-
68
- rule(:extended_dimension_rules) do
69
- spaces? >> extender >> spaces? >> dimension_rules_int_exp.as(:sequence)
70
- end
71
- rule(:dimension_rules_named_exp) do
72
- ((opening_paren.as(:open_paren) >> dimension_rules_int_exp.as(:int_exp) >> closing_paren.as(:close_paren)).as(:int_exp) >> spaces? >> (dimension_rules_int_exp | extended_dimension_rules).as(:sequence).maybe) |
73
- dimension_rules_int_exp
74
- end
75
-
76
- # Rules for dimensions
77
- rule(:sqrt_dimensions) do
78
- sqrt >> opening_paren >> dimension_rules_int_exp.as(:sqrt) >> closing_paren
79
- end
80
-
81
- # Rules for sequence
82
- rule(:sqrt_sequence) do
83
- sqrt >> opening_paren >> prefixes_units_int_exp.as(:sqrt) >> closing_paren
84
- end
85
- end
86
- end
data/lib/unitsml/parse.rb DELETED
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "parslet"
4
-
5
- module Unitsml
6
- class Parse < Parslet::Parser
7
- include IntermediateExpRules
8
-
9
- rule(:sqrt) { str("sqrt") }
10
- rule(:power) { str("^") >> slashed_number_int_exp.as(:power_numerator) }
11
- rule(:hyphen) { str("-") }
12
- rule(:number) { hyphen.maybe >> match(/[0-9]/).repeat(1) }
13
- rule(:spaces) { match(/\s/).repeat(1) }
14
- rule(:spaces?) { spaces.maybe }
15
-
16
- rule(:extender) { (forward_slashes | str("*")).as(:extender) }
17
- rule(:sequence) do
18
- (single_letter_prefixes >> units) | (double_letter_prefixes >> units) | units
19
- end
20
-
21
- rule(:opening_paren) { str("(") }
22
- rule(:closing_paren) { str(")") }
23
- rule(:unit_and_power) { units >> power.maybe }
24
- rule(:slashed_number) do
25
- (number >> (forward_slashes >> number).maybe).as(:integer)
26
- end
27
- rule(:forward_slashes) { str("//") | str("/") }
28
- rule(:dimension_rules) do
29
- (sqrt_dimensions | powered_dimensions) >> extended_dimension_rules.maybe
30
- end
31
- rule(:powered_dimensions) { dimensions >> power.maybe }
32
-
33
- rule(:units) do
34
- @@filtered_units ||= arr_to_expression(::Unitsml::Unitsdb.units.filtered,
35
- "unit")
36
- end
37
-
38
- rule(:single_letter_prefixes) do
39
- @@prefixes1 ||= arr_to_expression(::Unitsml::Unitsdb.prefixes_by_size(1),
40
- "prefix")
41
- end
42
-
43
- rule(:double_letter_prefixes) do
44
- @@prefixes2 ||= arr_to_expression(::Unitsml::Unitsdb.prefixes_by_size(2),
45
- "prefix")
46
- end
47
-
48
- rule(:dimensions) do
49
- @@dimensions ||= arr_to_expression(
50
- ::Unitsml::Unitsdb.dimensions.parsables.keys, "dimension"
51
- )
52
- end
53
-
54
- rule(:prefixes_units) do
55
- (sqrt_sequence >> extended_prefixed_units.maybe) |
56
- (str("1").as(:unit) >> extended_prefixed_units.maybe) |
57
- (unit_and_power >> extended_prefixed_units) |
58
- (unit_and_power >> (any.absent? | (extender | opening_paren | closing_paren | spaces).present?)) |
59
- (double_letter_prefixes >> unit_and_power >> extended_prefixed_units) |
60
- (double_letter_prefixes >> unit_and_power) |
61
- (single_letter_prefixes >> unit_and_power >> extended_prefixed_units) |
62
- (single_letter_prefixes >> unit_and_power)
63
- end
64
-
65
- rule(:expression) do
66
- prefixes_units_int_exp |
67
- dimension_rules_int_exp |
68
- (single_letter_prefixes >> hyphen) |
69
- (double_letter_prefixes >> hyphen)
70
- end
71
-
72
- root :expression
73
-
74
- def arr_to_expression(arr, file_name)
75
- array = arr&.flatten&.compact&.sort_by(&:length)&.reverse
76
- array&.reduce do |expression, expr_string|
77
- expression = str(expression).as(file_name.to_sym) if expression.is_a?(String)
78
- expression | str(expr_string).as(file_name.to_sym)
79
- end
80
- end
81
- end
82
- end