EXU: add dimensional powers and area volume units

This commit is contained in:
2026-08-02 17:39:28 -04:00
parent 171fd670af
commit a4b9d04ff4
3 changed files with 29 additions and 3 deletions

View File

@@ -18,6 +18,12 @@ const definitions: UnitDefinition[] = [
{ id: 'm', symbol: 'm', dimension: 'length', factor: 1000 },
{ id: 'in', symbol: 'in', dimension: 'length', factor: 25.4 },
{ id: 'ft', symbol: 'ft', dimension: 'length', factor: 304.8 },
{ id: 'mm2', symbol: 'mm2', dimension: 'area', factor: 1 },
{ id: 'cm2', symbol: 'cm2', dimension: 'area', factor: 100 },
{ id: 'm2', symbol: 'm2', dimension: 'area', factor: 1_000_000 },
{ id: 'mm3', symbol: 'mm3', dimension: 'volume', factor: 1 },
{ id: 'cm3', symbol: 'cm3', dimension: 'volume', factor: 1_000 },
{ id: 'm3', symbol: 'm3', dimension: 'volume', factor: 1_000_000_000 },
{ id: 'deg', symbol: 'deg', dimension: 'angle', factor: 1 },
{ id: 'rad', symbol: 'rad', dimension: 'angle', factor: 180 / Math.PI },
{ id: '%', symbol: '%', dimension: 'percent', factor: 1 },
@@ -63,7 +69,7 @@ const tokenize = (source: string): Token[] => {
while (index < source.length) {
const character = source[index]
if (/\s/.test(character)) { index += 1; continue }
if ('+-*/(),'.includes(character)) { tokens.push({ kind: 'operator', text: character, position: index }); index += 1; continue }
if ('+-*/^(),'.includes(character)) { tokens.push({ kind: 'operator', text: character, position: index }); index += 1; continue }
if (character === '%') { tokens.push({ kind: 'identifier', text: character, position: index }); index += 1; continue }
const number = source.slice(index).match(/^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/)
if (number) { tokens.push({ kind: 'number', text: number[0], position: index }); index += number[0].length; continue }
@@ -95,6 +101,15 @@ const divide = (left: Quantity, right: Quantity): Quantity => {
throw new TypeError(`Cannot divide ${left.dimension} by ${right.dimension}.`)
}
const power = (base: Quantity, exponent: Quantity): Quantity => {
if (exponent.dimension !== 'dimensionless' || !Number.isInteger(exponent.value)) throw new TypeError('Exponent must be a dimensionless integer.')
if (base.dimension === 'dimensionless') return { value: Math.pow(base.value, exponent.value), dimension: 'dimensionless' }
if (exponent.value === 1) return { ...base }
if (exponent.value === 2 && base.dimension === 'length') return { value: base.value ** 2, dimension: 'area' }
if (exponent.value === 3 && base.dimension === 'length') return { value: base.value ** 3, dimension: 'volume' }
throw new TypeError(`Cannot raise ${base.dimension} to the power ${exponent.value}.`)
}
const evaluateFunction = (name: string, args: Quantity[]): Quantity => {
const normalized = name.toLowerCase()
if (normalized === 'abs' || normalized === 'round') {
@@ -170,7 +185,13 @@ class QuantityParser {
private parseUnary(): Quantity {
if (this.match('+')) return this.parseUnary()
if (this.match('-')) { const value = this.parseUnary(); return { ...value, value: -value.value } }
return this.parsePrimary()
return this.parsePower()
}
private parsePower(): Quantity {
const base = this.parsePrimary()
if (!this.match('^')) return base
return power(base, this.parseUnary())
}
private parsePrimary(): Quantity {