Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion lib/unit/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,30 @@ def validate_unit(units)
end
end

# Parse a unit expression into a unit definition: an array of
# <tt>[factor, unit, exponent]</tt> 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
# (<tt>*</tt>, <tt>·</tt>, or juxtaposition), division (<tt>/</tt>),
# exponentiation (<tt>^</tt>, <tt>**</tt>), 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 <tt>[]</tt> — the same definition produced by
# <tt>Unit(1, "")</tt> — 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|
Expand Down Expand Up @@ -87,7 +111,7 @@ def parse_unit(expr)
end
end
compute(result, stack.pop) while !stack.empty?
result.last
result.last || []
end

private
Expand Down
23 changes: 23 additions & 0 deletions spec/error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions spec/system_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading