diff --git a/lib/unit/system.rb b/lib/unit/system.rb index ccd57e7..3783b39 100644 --- a/lib/unit/system.rb +++ b/lib/unit/system.rb @@ -60,6 +60,30 @@ def validate_unit(units) end end + # Parse a unit expression into a unit definition: an array of + # [factor, unit, exponent] triples suitable for +Unit.new+ and + # +validate_unit+. + # + # The expression is tokenized (see +TOKENIZER+) and evaluated with a + # shunting-yard pass over +OPERATOR+, supporting multiplication + # (*, ·, or juxtaposition), division (/), + # exponentiation (^, **), grouping parentheses, and + # numeric literals. + # + # system.parse_unit("m/s^2") # => [[:one, 1, 1], [:meter, ..., 1], [:second, ..., -2]] + # + # An empty or whitespace-only expression has no tokens and yields the + # *dimensionless* unit [] — the same definition produced by + # Unit(1, "") — so conversion stays consistent with construction + # rather than returning +nil+. + # + # system.parse_unit("") # => [] + # system.parse_unit(" ") # => [] + # + # Unrecognized glyphs survive lexing (see +SYMBOL+) and fail loudly as an + # "Undefined unit" +TypeError+ during validation rather than being dropped. + # + # Raises +SyntaxError+ on unbalanced parentheses. def parse_unit(expr) stack, result, implicit_mul = [], [], false expr.to_s.scan(TOKENIZER).each do |tok| @@ -87,7 +111,7 @@ def parse_unit(expr) end end compute(result, stack.pop) while !stack.empty? - result.last + result.last || [] end private diff --git a/spec/error_spec.rb b/spec/error_spec.rb index 71747e7..d0e55a1 100644 --- a/spec/error_spec.rb +++ b/spec/error_spec.rb @@ -26,4 +26,27 @@ end end + describe "converting to an empty (dimensionless) unit string" do + it "treats #in(\"\") as the dimensionless unit" do + expect(Unit(1, "").in("")).to eq(Unit(1, "")) + end + + it "treats #in(\" \") (whitespace-only) as the dimensionless unit" do + expect(Unit(1, "").in(" ")).to eq(Unit(1, "")) + end + + it "treats #in!(\"\") as the dimensionless unit" do + expect(Unit(1, "").in!("")).to eq(Unit(1, "")) + end + + it "treats #in!(\" \") (whitespace-only) as the dimensionless unit" do + expect(Unit(1, "").in!(" ")).to eq(Unit(1, "")) + end + + it "raises a clean TypeError (not NoMethodError) from #in! for an incompatible unit" do + unit = Unit(5, "m/s") + expect { unit.in!("") }.to raise_error(TypeError) + end + end + end diff --git a/spec/system_spec.rb b/spec/system_spec.rb index c5bcd2b..32ed18b 100644 --- a/spec/system_spec.rb +++ b/spec/system_spec.rb @@ -172,4 +172,16 @@ expect(Unit(1, "MeV", system).unit).to eq(Unit(1, "megaelectronvolt", system).unit) end end + + describe "#parse_unit with an empty or whitespace-only expression" do + before { system.load(:si) } + + it "returns [] (the dimensionless unit) for an empty string" do + expect(system.parse_unit("")).to eq([]) + end + + it "returns [] (the dimensionless unit) for a whitespace-only string" do + expect(system.parse_unit(" ")).to eq([]) + end + end end