Add programmatic composite unit builder#72
Open
suleman-uzair wants to merge 18 commits into
Open
Conversation
added 9 commits
July 1, 2026 23:08
Compose units from parts instead of only parsing strings. Adds the multiplication and division operator DSL (on Unit, Dimension and Formula via a Composable mixin) and the Unitsml.compose keyword form, both producing a Formula whose output matches the equivalent parsed string across every format. Units are referenced by symbol id or short name, powers come from the constructor, and quantity/name/multiplier metadata are passed to the render methods. Also resolves quantities by name and short slug (Quantities#find_by_name) and emits a canonical Quantity element with the NIST xml:id, quantityType and dimensionURL. Implements #2.
There was a problem hiding this comment.
Pull request overview
This PR introduces a programmatic composite-unit construction API to complement Unitsml.parse, adding operator (*//) and fluent-chain composition for Unit/Dimension/Formula, plus a Unitsml.compose keyword builder. It also enhances quantity resolution (by name as well as id) and updates XML emission to output canonical NIST xml:id/quantityType metadata and better dimensionURL handling for composites.
Changes:
- Added composition framework (
Unitsml::Compose::*) powering operator DSL, fluent chain, andUnitsml.compose. - Added new validation errors for builder inputs (unknown unit/dimension/prefix, mixed terms, invalid powers, empty composition, invalid entries).
- Improved quantity lookup and XML emission (canonical NIST id + quantityType, case-insensitive name lookup, explicit-quantity behavior).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/unitsml/utility_quantity_spec.rb | Adds tests for quantity lookup by name and canonical <Quantity> XML emission. |
| spec/unitsml/composite_spec.rb | Adds extensive coverage for programmatic composition surfaces and validation behavior. |
| lib/unitsml/utility.rb | Extends quantity resolution and quantity XML emission; threads composite dimension info into <Quantity>. |
| lib/unitsml/unitsdb/quantities.rb | Adds find_by_name for case-insensitive English/short quantity resolution. |
| lib/unitsml/unit.rb | Adds composition mixin; validates unit references and prefixes in the constructor path. |
| lib/unitsml/opal.rb | Requires new compose components and new error classes in Opal builds. |
| lib/unitsml/number.rb | Normalizes stored numeric value to a String to avoid downstream crashes. |
| lib/unitsml/formula.rb | Integrates composed-formula term contribution; adds render-option overrides for metadata; passes composite dims to quantity emission. |
| lib/unitsml/errors/unknown_unit_error.rb | New error for invalid unit references. |
| lib/unitsml/errors/unknown_prefix_error.rb | New error for invalid prefix references. |
| lib/unitsml/errors/unknown_dimension_error.rb | New error for invalid dimension references. |
| lib/unitsml/errors/mixed_terms_error.rb | New error for unit/dimension mixing in one expression. |
| lib/unitsml/errors/invalid_unit_entry_error.rb | New error for invalid compose entries/operands/metadata. |
| lib/unitsml/errors/invalid_power_error.rb | New error for powers the parser cannot express (e.g., decimal floats). |
| lib/unitsml/errors/empty_composition_error.rb | New error for missing/empty units: / dimensions: in compose. |
| lib/unitsml/errors.rb | Autoloads newly introduced builder-related error classes. |
| lib/unitsml/dimension.rb | Adds composition mixin and parser-style source text synthesis for dimensions. |
| lib/unitsml/compose/term_tree.rb | Adds a shared traversal/mapping abstraction for composition term trees. |
| lib/unitsml/compose/composite.rb | Implements Unitsml.compose keyword form normalization/validation/coercion. |
| lib/unitsml/compose/composable.rb | Implements operator DSL + fluent chain + metadata attachment. |
| lib/unitsml/compose/builder.rb | Builds composed root Formulas without mutating operands; validates mixed terms and metadata. |
| lib/unitsml/compose.rb | Adds shared validators for unit/dimension refs, prefixes, and exponent normalization. |
| lib/unitsml.rb | Exposes Unitsml.compose and autoloads Compose. |
| docs/README.adoc | Documents programmatic composition APIs, validation rules, and render-time metadata overrides. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
suleman-uzair
marked this pull request as ready for review
July 8, 2026 17:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR:
Unitsml.parse.Formularenders through every existing format (MathML, LaTeX, AsciiMath, HTML, Unicode, UnitsML XML, Plurimath), and its representation mirrors the parser's — exponents areUnitsml::Number, and a single division matches the parsed string (Unitsml::Unit.new("W") / Unitsml::Unit.new("m")==parse("W/m")).Three ways to build
Operator DSL —
*and/onUnit,DimensionandFormula:Fluent chain —
#unit/#dimensionare sugar over*; metadata comes last:Keyword form —
Unitsml.compose, passing eitherunits:ordimensions::Separators
Like the parser, the
/operator keeps the/separator and inverts the right-hand operand's power.An explicit separator can also be placed with
#extender/#ext, accepting exactly the parser's grammar (*,/,//); anUnitsml::Extenderalso works directly as an operand:The rendered separator glyph is otherwise a display choice — pass
multiplier:(:space,:nospace, or a custom string such as"·") to anyto_*method.Validation
Input is validated at the compose boundary, so bad input fails fast with a
Unitsml::Errors::*(all< Unitsml::Errors::BaseError) instead of crashing at render: an unknown unit/dimension/prefix, a units/dimensions mix, an empty composition, a power the parser cannot express (e.g. a decimal exponent), or a misplaced separator (Errors::MisplacedExtenderError).Units and dimensions are referenced by symbol id, exactly as in a parsed string.
This PR also resolves
quantityby NIST id or name and emits a canonical<Quantity>element in XML.Closes #2