exprify 1.0.2 → 1.0.3

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.
package/dist/exprify.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * exprify v1.0.2
2
+ * exprify v1.0.3
3
3
  * (c) 2026 Nirmal Paul and other contributors
4
4
  *
5
5
  * Released under the GPL-3.0 License
@@ -1,3 +1,3 @@
1
- /*! exprify v1.0.2 | * (c) 2026 Nirmal Paul and contributors | GPL-3.0 License*/
1
+ /*! exprify v1.0.3 | * (c) 2026 Nirmal Paul and contributors | GPL-3.0 License*/
2
2
  !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Exprify=t()}(this,function(){"use strict";const e=e=>e&&"object"==typeof e&&"DenseMatrix"===e.exprify&&"data"in e&&"size"in e,t=e=>Array.isArray(e)?e.map(t):e,r=e=>{if(Array.isArray(e)&&e.every(Array.isArray))return[e.length,e[0]?.length||0];if(Array.isArray(e))return[e.length];throw new Error("Matrix data must be an array")},n=e=>({exprify:"DenseMatrix",data:t(e),size:r(e)}),o=r=>e(r)?t(r.data):r,i=t=>e(t)?JSON.stringify(t):Array.isArray(t)||t&&"object"==typeof t?JSON.stringify(t,(t,r)=>(e(r),r)):t;function a(e,t={}){const r=t.variables,i=t.functions,s=t.units,l=e=>e&&"object"==typeof e&&"value"in e&&"unit"in e,u=e=>e&&"object"==typeof e&&"re"in e&&"im"in e,c=e=>Array.isArray(e)&&e.length>0&&e.every(Array.isArray),p=e=>{if(e=o(e),c(e))return e.map(e=>[...e]);if(Array.isArray(e))return[e];throw new Error("Expected matrix-compatible value")},m=e=>{if("number"!=typeof e||!Number.isInteger(e)||e<1)throw new Error("Matrix indices must be positive integers");return e-1},f=(e,r)=>{if((n=e)&&"object"==typeof n&&"SliceExpression"===n.type){const n=null==e.start?1:a(e.start,t),o=null==e.end?r:a(e.end,t),i=m(n),s=m(o);if(s<i)return[];const l=[];for(let e=i;e<=s;e++)l.push(e);return l}var n;return[m(a(e,t))]},y=e=>{if(u(e))return e;if("number"==typeof e)return{re:e,im:0};throw new Error("Complex arithmetic only supports numbers")},h=e=>0===e.im?e.re:e;switch(e.type){case"Literal":return e.value;case"ImaginaryLiteral":return{re:0,im:e.value};case"UnitLiteral":return{value:e.value,unit:e.unit};case"Identifier":return r.get(e.name);case"AssignmentExpression":{const i=a(e.right,t);if("Identifier"===e.left.type)return r.set(e.left.name,i),"ArrayExpression"===e.right.type?n(o(i)):i;if("IndexExpression"===e.left.type&&"Identifier"===e.left.object.type){const t=((e,t,r)=>{const n=c(e)?e.map(e=>[...e]):Array.isArray(e)?[e.slice()]:[],o=t[0],i=t[1];if(!o)throw new Error("Matrix assignment requires at least one index");const a=Math.max(n.length,1),s=f(o,a);if(1===t.length){const e=c(r)?r:p(r);if(e.length!==s.length)throw new Error("Assigned row count does not match slice");return s.forEach((t,r)=>{n[t]=[...e[r]]}),{updatedMatrix:n,selectionResult:1===s.length?[n[s[0]]]:s.map(e=>[n[e]])}}const l=Math.max(...n.map(e=>e.length),0,1),u=f(i,l),m=p(r);if(m.length!==s.length)throw new Error("Assigned row count does not match matrix slice");return m.forEach((e,t)=>{if(e.length!==u.length)throw new Error("Assigned column count does not match matrix slice")}),s.forEach((e,t)=>{n[e]||(n[e]=[]),u.forEach((r,o)=>{n[e][r]=m[t][o]})}),{updatedMatrix:n,selectionResult:1===s.length?[u.map(e=>n[s[0]][e])]:s.map(e=>u.map(t=>n[e][t]))}})(r.get(e.left.object.name),e.left.selectors,i);return r.set(e.left.object.name,t.updatedMatrix),t.selectionResult}throw new Error("Invalid assignment target")}case"FunctionAssignmentExpression":{if("="!==e.operator)throw new Error(`Operator ${e.operator} is not supported for function definitions`);const r=(...r)=>{const n=t.withScope(((e,t)=>{const r={};return e.forEach((e,n)=>{r[e]=t[n]}),r})(e.params,r));return a(e.right,n)};return i.register(e.left.name,r),r}case"UnaryExpression":{const r=a(e.argument,t);switch(e.operator){case"-":return u(r)?h({re:-r.re,im:-r.im}):-r;case"!":return!r}throw new Error(`Unknown unary operator ${e.operator}`)}case"BinaryExpression":{let r=a(e.left,t),n=a(e.right,t);if(l(r)||l(n)){if(!s)throw new Error("Unit system not available");return s.compute(e.operator,r,n)}if("*"===e.operator&&(Array.isArray(r)||Array.isArray(n)))return((e,t)=>{const r=p(e),n=p(t);if(r[0].length!==n.length)throw new Error("Matrix dimensions do not allow multiplication");return r.map(e=>n[0].map((t,r)=>e.reduce((e,t,o)=>e+t*n[o][r],0)))})(r,n);if(u(r)||u(n))return((e,t,r)=>{const n=y(t),o=y(r);switch(e){case"+":return h({re:n.re+o.re,im:n.im+o.im});case"-":return h({re:n.re-o.re,im:n.im-o.im});case"*":return h({re:n.re*o.re-n.im*o.im,im:n.re*o.im+n.im*o.re});case"/":{const e=o.re**2+o.im**2;if(0===e)throw new Error("Division by zero");return h({re:(n.re*o.re+n.im*o.im)/e,im:(n.im*o.re-n.re*o.im)/e})}default:throw new Error(`Operator ${e} is not supported for complex numbers`)}})(e.operator,r,n);switch(e.operator){case"+":return r+n;case"-":return r-n;case"*":return r*n;case"/":return r/n;case"%":return r%n;case"^":return r**n;case">":return r>n;case"<":return r<n;case">=":return r>=n;case"<=":return r<=n;case"==":return r===n}throw new Error(`Unknown operator ${e.operator}`)}case"LogicalExpression":{const r=a(e.left,t);if("&&"===e.operator)return r&&a(e.right,t);if("||"===e.operator)return r||a(e.right,t);if("??"===e.operator)return r??a(e.right,t);throw new Error(`Unknown logical operator ${e.operator}`)}case"CallExpression":{const r=e.callee.name;return i.get(r)(...e.arguments.map(e=>a(e,t)))}case"PipelineExpression":{const r=a(e.left,t);if("CallExpression"===e.right.type){const n=e.right.callee.name;return i.get(n)(...[r,...e.right.arguments.map(e=>a(e,t))])}if("Identifier"===e.right.type){return i.get(e.right.name)(r)}throw new Error("Invalid pipeline target")}case"UnitConversion":{const r=a(e.from,t);if(!l(r))throw new Error("Left side must be a unit value");if(!s)throw new Error("Unit system not available");return s.convert(r.value,r.unit,e.to)}case"ArrayExpression":return e.elements.map(e=>a(e,t));case"IndexExpression":return((e,t)=>{const r=p(e);if(1===t.length){const e=f(t[0],r.length).map(e=>{if(e>=r.length)throw new Error("Row index out of range");return[...r[e]]});return 1===e.length?e[0]:e}const n=f(t[0],r.length),o=f(t[1],r[0]?.length||0),i=n.map(e=>{if(e>=r.length)throw new Error("Row index out of range");return o.map(t=>{if(t>=r[e].length)throw new Error("Column index out of range");return r[e][t]})});return 1===n.length&&1===o.length?i[0][0]:1===n.length?i[0]:1===o.length?i.map(e=>[e[0]]):i})(a(e.object,t),e.selectors);case"ObjectExpression":{const r={};for(let n of e.properties)r[n.key]=a(n.value,t);return r}case"MemberExpression":{const r=a(e.object,t);if(e.optional&&null==r)return;return r[e.property.name]}default:throw new Error(`Unknown AST node type: ${e.type}`)}}function s({variables:e,functions:t,units:r,evaluate:n}){if(!e)throw new Error("Variable store missing");if(!t)throw new Error("Function registry missing");if(!r)throw new Error("Units list missing");if(!n)throw new Error("evaluate function missing");return{variables:e,functions:t,units:r,evaluate:n,withScope(o={}){const i={...e.all?.(),...o};return s({functions:t,evaluate:n,units:r,variables:{get:e=>i[e],set:(e,t)=>i[e]=t,all:()=>i}})}}}const l=(e,t)=>typeof e==typeof t&&("number"==typeof e||"bigint"==typeof e),u=Object.freeze({power:function(e,t){if(l(e,t))return e**t;throw new Error("Invalid types for ^")},multiply:function(e,t){if(l(e,t))return e*t;throw new Error("Invalid types for *")},divide:function(e,t){if(l(e,t)){if(0===t)throw new Error("Division by zero");return e/t}throw new Error("Invalid types for /")},add:function(e,t){if(l(e,t))return e+t;if("string"==typeof e&&"string"==typeof t)return e+t;throw new Error("Invalid types for +")},subtract:function(e,t){if(l(e,t))return e-t;throw new Error("Invalid types for -")},modulus:function(e,t){if(l(e,t))return e%t;throw new Error("Invalid types for %")}});const c={length:{m:{value:1,unit:"meter",symbol:"m"},cm:{value:.01,unit:"centimeter",symbol:"cm"},mm:{value:.001,unit:"millimeter",symbol:"mm"},km:{value:1e3,unit:"kilometer",symbol:"km"},um:{value:1e-6,unit:"micrometer",symbol:"um",note:"also called micron"},nm:{value:1e-9,unit:"nanometer",symbol:"nm"},px:{value:264583e-9,unit:"pixel",symbol:"px",note:"96dpi standard"},em:{value:.004233328,unit:"em",symbol:"em",note:"1em = 16px by default"},rem:{value:.004233328,unit:"rem",symbol:"rem",note:"root em = 16px by default"},pt:{value:352778e-9,unit:"point",symbol:"pt",note:"1pt = 1/72 inch"},pc:{value:.00423333,unit:"pica",symbol:"pc",note:"1pc = 12pt"},inch:{value:.0254,unit:"inch",symbol:"in"},ft:{value:.3048,unit:"foot",symbol:"ft"},yd:{value:.9144,unit:"yard",symbol:"yd"},mi:{value:1609.344,unit:"mile",symbol:"mi"},thou:{value:254e-7,unit:"mil",symbol:"thou",note:"thousandth of an inch"},furlong:{value:201.168,unit:"furlong",symbol:"fur",note:"220 yards"},nmi:{value:1852,unit:"nautical mile",symbol:"nmi"},fathom:{value:1.8288,unit:"fathom",symbol:"fathom"},au:{value:1496e8,unit:"astronomical unit",symbol:"AU"},ly:{value:94607e11,unit:"light year",symbol:"ly"},pc:{value:30857e12,unit:"parsec",symbol:"pc"}},weight:{mg:{value:1e-6,unit:"milligram",symbol:"mg"},g:{value:.001,unit:"gram",symbol:"g"},kg:{value:1,unit:"kilogram",symbol:"kg"},t:{value:1e3,unit:"tonne",symbol:"t",note:"metric ton"},lb:{value:.453592,unit:"pound",symbol:"lb"},oz:{value:.0283495,unit:"ounce",symbol:"oz"},stone:{value:6.35029,unit:"stone",symbol:"st",note:"1 stone = 14 lb"}},time:{s:{value:1,unit:"second",symbol:"s"},min:{value:60,unit:"minute",symbol:"min"},h:{value:3600,unit:"hour",symbol:"h"},day:{value:86400,unit:"day",symbol:"d"},week:{value:604800,unit:"week",symbol:"wk"},month:{value:2629800,unit:"month",symbol:"mo",note:"average month = 30.44 days"},year:{value:31557600,unit:"year",symbol:"yr",note:"average year = 365.25 days"}},voltage:{V:{value:1,unit:"volt",symbol:"V"},mV:{value:.001,unit:"millivolt",symbol:"mV"},kV:{value:1e3,unit:"kilovolt",symbol:"kV"},MV:{value:1e6,unit:"megavolt",symbol:"MV"},GV:{value:1e9,unit:"gigavolt",symbol:"GV"},statV:{value:299.792458,unit:"statvolt",symbol:"statV",note:"CGS unit"},abV:{value:1e-8,unit:"abvolt",symbol:"abV",note:"CGS electromagnetic unit"}},frequency:{Hz:{value:1,unit:"hertz",symbol:"Hz",note:"1 cycle per second"},kHz:{value:1e3,unit:"kilohertz",symbol:"kHz"},MHz:{value:1e6,unit:"megahertz",symbol:"MHz"},GHz:{value:1e9,unit:"gigahertz",symbol:"GHz"},THz:{value:1e12,unit:"terahertz",symbol:"THz"}},power:{W:{value:1,unit:"watt",symbol:"W",note:"1 joule per second"},mW:{value:.001,unit:"milliwatt",symbol:"mW"},kW:{value:1e3,unit:"kilowatt",symbol:"kW"},MW:{value:1e6,unit:"megawatt",symbol:"MW"},GW:{value:1e9,unit:"gigawatt",symbol:"GW"},HP:{value:745.7,unit:"horsepower",symbol:"HP",note:"mechanical HP = 745.7 W"},kcal_per_h:{value:1.163,unit:"kilocalorie per hour",symbol:"kcal/h",note:"= 1.163 W"},BTU_per_h:{value:.29307107,unit:"BTU per hour",symbol:"BTU/h",note:"= 0.293 W"}},sound:{dB:{value:1,unit:"decibel",symbol:"dB",note:"logarithmic unit of sound intensity"},dBA:{value:1,unit:"A-weighted decibel",symbol:"dBA",note:"Adjusted for human hearing"},dBC:{value:1,unit:"C-weighted decibel",symbol:"dBC",note:"Flat weighting for high-level sounds"}},temperature:{K:{value:1,unit:"kelvin",symbol:"K"},C:{value:1,unit:"Celsius",symbol:"°C",note:"°C → K: add 273.15"},F:{value:1,unit:"Fahrenheit",symbol:"°F",note:"°F → K: (°F - 32) * 5/9 + 273.15"}},pressure:{Pa:{value:1,unit:"pascal",symbol:"Pa"},kPa:{value:1e3,unit:"kilopascal",symbol:"kPa"},MPa:{value:1e6,unit:"megapascal",symbol:"MPa"},bar:{value:1e5,unit:"bar",symbol:"bar"},atm:{value:101325,unit:"atmosphere",symbol:"atm"},psi:{value:6894.757,unit:"pound per square inch",symbol:"psi"},mmHg:{value:133.322,unit:"millimeter of mercury",symbol:"mmHg"}},energy:{J:{value:1,unit:"joule",symbol:"J"},kJ:{value:1e3,unit:"kilojoule",symbol:"kJ"},cal:{value:4.184,unit:"calorie",symbol:"cal"},kcal:{value:4184,unit:"kilocalorie",symbol:"kcal"},eV:{value:160218e-24,unit:"electronvolt",symbol:"eV"},BTU:{value:1055.06,unit:"BTU",symbol:"BTU"}},force:{N:{value:1,unit:"newton",symbol:"N"},kN:{value:1e3,unit:"kilonewton",symbol:"kN"},lbf:{value:4.44822,unit:"pound-force",symbol:"lbf"},kgf:{value:9.80665,unit:"kilogram-force",symbol:"kgf"},dyne:{value:1e-5,unit:"dyne",symbol:"dyn"}},area:{m2:{value:1,unit:"square meter",symbol:"m²"},cm2:{value:1e-4,unit:"square centimeter",symbol:"cm²"},km2:{value:1e6,unit:"square kilometer",symbol:"km²"},acre:{value:4046.856,unit:"acre",symbol:"acre"},hectare:{value:1e4,unit:"hectare",symbol:"ha"},ft2:{value:.092903,unit:"square foot",symbol:"ft²"},yd2:{value:.836127,unit:"square yard",symbol:"yd²"}},volume:{m3:{value:1,unit:"cubic meter",symbol:"m³"},L:{value:.001,unit:"liter",symbol:"L"},mL:{value:1e-6,unit:"milliliter",symbol:"mL"},gallon:{value:.00378541,unit:"US gallon",symbol:"gal"},pint:{value:473176e-9,unit:"US pint",symbol:"pt"},floz:{value:29574e-9,unit:"US fluid ounce",symbol:"fl oz"}},current:{A:{value:1,unit:"ampere",symbol:"A"},mA:{value:.001,unit:"milliampere",symbol:"mA"},uA:{value:1e-6,unit:"microampere",symbol:"uA"},kA:{value:1e3,unit:"kiloampere",symbol:"kA"}},resistance:{ohm:{value:1,unit:"ohm"},kohm:{value:1e3,unit:"kiloohm"},megaohm:{value:1e6,unit:"megaohm"},S:{value:1,unit:"siemens",symbol:"S",note:"conductance"}},capacitance:{F:{value:1,unit:"farad",symbol:"F"},mF:{value:.001,unit:"millifarad"},uF:{value:1e-6,unit:"microfarad"}},inductance:{H:{value:1,unit:"henry",symbol:"H"},mH:{value:.001,unit:"millihenry",symbol:"mH"},uH:{value:1e-6,unit:"microhenry",symbol:"uH"}},light:{cd:{value:1,unit:"candela",symbol:"cd"},lm:{value:1,unit:"lumen",symbol:"lm"},lx:{value:1,unit:"lux",symbol:"lx"}},data:{bit:{value:1,unit:"bit",symbol:"bit"},B:{value:8,unit:"byte",symbol:"B"},KB:{value:8e3,unit:"kilobyte",symbol:"KB"},MB:{value:8e6,unit:"megabyte",symbol:"MB"},GB:{value:8e9,unit:"gigabyte",symbol:"GB"},TB:{value:8e12,unit:"terabyte",symbol:"TB"}},angle:{deg:{value:1,unit:"degree",symbol:"°"},rad:{value:57.2958,unit:"radian",symbol:"rad",note:"1 rad = 57.2958°"},grad:{value:.9,unit:"grad",symbol:"grad",note:"1 grad = 0.9°"}},radiation:{Gy:{value:1,unit:"gray",symbol:"Gy",note:"Absorbed dose: 1 Gy = 1 J/kg"},mGy:{value:.001,unit:"milligray",symbol:"mGy"},rad:{value:.01,unit:"rad",symbol:"rad",note:"1 rad = 0.01 Gy"},Sv:{value:1,unit:"sievert",symbol:"Sv",note:"Biological effect dose equivalent"},mSv:{value:.001,unit:"millisievert",symbol:"mSv"},rem:{value:.01,unit:"rem",symbol:"rem",note:"1 rem = 0.01 Sv"},Bq:{value:1,unit:"becquerel",symbol:"Bq",note:"1 decay per second"},kBq:{value:1e3,unit:"kilobecquerel",symbol:"kBq"},MBq:{value:1e6,unit:"megabecquerel",symbol:"MBq"},GBq:{value:1e9,unit:"gigabecquerel",symbol:"GBq"},Ci:{value:37e9,unit:"curie",symbol:"Ci",note:"1 Ci = 3.7 x 10¹⁰ decays per second"},mCi:{value:37e6,unit:"millicurie",symbol:"mCi"}}},p=/^[a-zA-Z_$][a-zA-Z0-9_$]*$/;function m(e={}){let t=Object.create(null);for(const r in e)t[r]=e[r];return{set(e,r,{override:n=!0}={}){if("string"!=typeof e||!e)throw new Error("Variable name must be a non-empty string");if(!p.test(e))throw new Error(`Variable Name Error: '${e}' is not a valid variable name`);if(void 0===r)throw new Error(`Variable Value Error: '${e}' cannot be undefined`);if(!n&&e in variablesDB)throw new Error(`Variable '${e}' already exists`);t[e]=r},get:e=>t[e],has:e=>Object.prototype.hasOwnProperty.call(t,e),remove(e){delete t[e]},all:()=>({...t}),clear(){t=Object.create(null)},merge(e={}){for(const r in e)t[r]=e[r]},clone:()=>m(t)}}function f(e){if(e=o(e),!Array.isArray(e)||0===e.length)throw new Error("det() expects a non-empty matrix");if(!e.every(Array.isArray))throw new Error("det() expects a 2D matrix");const t=e.length;if(!e.every(e=>e.length===t))throw new Error("det() expects a square matrix");for(const t of e)for(const e of t)if("number"!=typeof e&&"bigint"!=typeof e)throw new Error("det() matrix values must be numeric")}function y(e){return f(e=o(e)),1===e.length?e[0][0]:2===e.length?e[0][0]*e[1][1]-e[0][1]*e[1][0]:e[0].reduce((t,r,n)=>{const o=e.slice(1).map(e=>e.filter((e,t)=>t!==n));return t+(n%2==0?r:-r)*y(o)},0)}function h(e){const t=o(e);if(!Array.isArray(t))throw new Error("Expected matrix data");return t}function d(e){const t=h(e).map(e=>[...e]);f(t);const r=t.length,o=Array.from({length:r},(e,t)=>t);for(let e=0;e<r;e++){let n=e,i=Math.abs(t[e][e]);for(let o=e+1;o<r;o++){const r=Math.abs(t[o][e]);r>i&&(i=r,n=o)}if(0===i)throw new Error("Matrix is singular");n!==e&&([t[e],t[n]]=[t[n],t[e]],[o[e],o[n]]=[o[n],o[e]]);for(let n=e+1;n<r;n++){t[n][e]/=t[e][e];for(let o=e+1;o<r;o++)t[n][o]-=t[n][e]*t[e][o]}}const i=t.map((e,t)=>e.map((e,r)=>t===r?1:t>r?e:0)),a=t.map((e,t)=>e.map((e,r)=>t<=r?e:0));return{L:n(i),U:n(a),p:o}}function v(e,t){const r=h(e).map(e=>[...e]),o=h(t).map(e=>[...e]);f(r),f(o);const i=r.length;if(o.length!==i)throw new Error("A and Q must have the same dimensions");const a=[],s=[];for(let e=0;e<i;e++)for(let t=0;t<i;t++){const n=new Array(i*i).fill(0);for(let o=0;o<i;o++)n[o*i+t]+=r[e][o],n[e*i+o]+=r[t][o];a.push(n),s.push(-o[e][t])}const l=function(e,t){const r=e.length,n=e.map((e,r)=>[...e,t[r]]);for(let e=0;e<r;e++){let t=e,o=Math.abs(n[e][e]);for(let i=e+1;i<r;i++){const r=Math.abs(n[i][e]);r>o&&(o=r,t=i)}if(0===o)throw new Error("Linear system is singular");t!==e&&([n[e],n[t]]=[n[t],n[e]]);const i=n[e][e];for(let t=e;t<=r;t++)n[e][t]/=i;for(let t=0;t<r;t++){if(t===e)continue;const o=n[t][e];for(let i=e;i<=r;i++)n[t][i]-=o*n[e][i]}}return n.map(e=>e[r])}(a,s),u=[];for(let e=0;e<i;e++)u.push(l.slice(e*i,(e+1)*i));return n(u)}function g(e,t){return e.reduce((e,r,n)=>e+r*t**n,0)}function b(e,t){const r=[...e].reverse(),n=[r[0]];for(let e=1;e<r.length-1;e++)n.push(r[e]+n[e-1]*t);const o=r[r.length-1]+n[n.length-1]*t;return{quotient:n.reverse(),remainder:o}}function w(e){const[t,r,n]=e,o=r**2-4*n*t;if(o<0)throw new Error("Only real roots are supported");const i=Math.sqrt(o);return[(-r+i)/(2*n),(-r-i)/(2*n)]}function E(e,t){return e.reduce((e,r,n)=>e+r*t[n],0)}function x(e){return Math.sqrt(E(e,e))}function A(e,t){return e.map(e=>e*t)}function k(e,t){return e.map((e,r)=>e-t[r])}function $(e,t){const r=function(e){const t=e.replace(/\s+/g,"");return t?t.replace(/-/g,"+-").split("+").filter(Boolean):[]}(e),n=new Map;for(const e of r)if(e.includes(t)){const[r,o]=e.split(t);let i;if(""===r||"+"===r)i=1;else if("-"===r)i=-1;else{const e=r.endsWith("*")?r.slice(0,-1):r;i=Number(e)}if(!Number.isFinite(i))throw new Error("Unsupported algebra term");let a=1;if(o){if(!o.startsWith("^"))throw new Error("Unsupported algebra term");a=Number(o.slice(1))}if(!Number.isInteger(a)||a<0)throw new Error("Only non-negative integer powers are supported");n.set(a,(n.get(a)||0)+i)}else{const t=Number(e);if(!Number.isFinite(t))throw new Error("Unsupported algebra term");n.set(0,(n.get(0)||0)+t)}return n}function M(e,t){const r=[...e.entries()].filter(([,e])=>0!==e).sort((e,t)=>t[0]-e[0]);return r.length?r.map(([e,r],n)=>{const o=r<0,i=Math.abs(r);let a;return a=0===e?`${i}`:1===e?1===i?t:`${i} * ${t}`:1===i?`${t}^${e}`:`${i} * ${t}^${e}`,0===n?o?`-${a}`:a:o?`- ${a}`:`+ ${a}`}).join(" "):"0"}const O={max:(...e)=>{if(!e.length)throw new Error("max() requires arguments");return Math.max(...e)},min:(...e)=>{if(!e.length)throw new Error("min() requires arguments");return Math.min(...e)},abs:e=>Math.abs(e),round:e=>Math.round(e),floor:e=>Math.floor(e),ceil:e=>Math.ceil(e),sqrt:e=>{if(e<0)throw new Error("sqrt() domain error");return Math.sqrt(e)},pow:(e,t)=>e**t,det:e=>y(e),polynomialRoot:(...e)=>function(...e){for(;e.length>1&&0===e[e.length-1];)e.pop();const t=e.length-1;if(t<1)throw new Error("polynomialRoot() expects at least a linear polynomial");if(1===t){const[t,r]=e;return[-t/r]}if(2===t)return w(e);if(3===t){const t=e[0];e[3];const r=[],n=Math.abs(t);for(let e=1;e<=Math.max(1,n);e++)n%e===0&&r.push(e,-e);for(const t of r)if(0===g(e,t))return[t,...w(b(e,t).quotient)]}throw new Error("polynomialRoot() currently supports degree up to 3")}(...e),lsolve:(e,t)=>function(e,t){const{L:r,U:o,p:i}=d(e),a=h(e),s=h(t),l=Array.isArray(s[0])?s.map(e=>e[0]):s;if(a.length!==l.length)throw new Error("Right-hand side dimension mismatch");const u=i.map(e=>l[e]),c=new Array(a.length).fill(0);for(let e=0;e<a.length;e++){c[e]=u[e];for(let t=0;t<e;t++)c[e]-=r.data[e][t]*c[t]}const p=new Array(a.length).fill(0);for(let e=a.length-1;e>=0;e--){p[e]=c[e];for(let t=e+1;t<a.length;t++)p[e]-=o.data[e][t]*p[t];p[e]/=o.data[e][e]}return n(p.map(e=>[e]))}(e,t),lup:e=>d(e),lyap:(e,t)=>v(e,t),qr:e=>function(e){const t=h(e).map(e=>[...e]);if(!t.length||!t.every(e=>e.length===t[0].length))throw new Error("qr() expects a rectangular matrix");const r=t.length,o=t[0].length,i=(a=t)[0].map((e,t)=>a.map(e=>e[t]));var a;const s=[];for(let e=0;e<o;e++){let t=[...i[e]];for(let r=0;r<s.length;r++){const n=E(s[r],i[e]);t=k(t,A(s[r],n))}const r=x(t);if(0===r)throw new Error("qr() requires linearly independent columns");s.push(A(t,1/r))}for(let e=0;s.length<r&&e<r;e++){let t=Array.from({length:r},(t,r)=>r===e?1:0);for(const e of s)t=k(t,A(e,E(e,t)));const n=x(t);n>1e-10&&s.push(A(t,1/n))}const l=Array.from({length:r},(e,t)=>s.map(e=>e[t])),u=Array.from({length:r},()=>Array(o).fill(0));for(let e=0;e<r;e++)for(let t=0;t<o;t++)u[e][t]=E(s[e],i[t]);return{Q:n(l),R:n(u)}}(e),simplify:e=>{if("string"!=typeof e)throw new Error("simplify() expects an expression string");return function(e){const t=e.replace(/\s+/g,"").match(/[a-zA-Z]+/),r=t?.[0]||"x";return M($(e,r),r)}(e)},derivative:(e,t="x")=>{if("string"!=typeof e||"string"!=typeof t)throw new Error("derivative() expects expression and variable strings");return function(e,t){const r=$(e,t),n=new Map;for(const[e,t]of r.entries())0!==e&&n.set(e-1,(n.get(e-1)||0)+t*e);return M(n,t)}(e,t)},sin:e=>Math.sin(e),cos:e=>Math.cos(e),tan:e=>Math.tan(e),asin:e=>Math.asin(e),acos:e=>Math.acos(e),atan:e=>Math.atan(e),log:e=>{if(e<=0)throw new Error("log() domain error");return Math.log(e)},log10:e=>{if(e<=0)throw new Error("log10() domain error");return Math.log10(e)},exp:e=>Math.exp(e),random:()=>Math.random(),and:(e,t)=>Boolean(e&&t),or:(e,t)=>Boolean(e||t),not:e=>!e,"!":e=>!e,eq:(e,t)=>e===t,neq:(e,t)=>e!==t,notEqual:(e,t)=>e!==t,gt:(e,t)=>e>t,greaterThan:(e,t)=>e>t,lt:(e,t)=>e<t,lessThan:(e,t)=>e<t,gte:(e,t)=>e>=t,greaterThanOrEqual:(e,t)=>e>=t,lte:(e,t)=>e<=t,lessThanOrEqual:(e,t)=>e<=t,clamp:(e,t,r)=>{if(t>r)throw new Error("clamp(): min > max");return Math.min(Math.max(e,t),r)},if:(e,t,r)=>e?t:r,typeof:e=>typeof e,length:e=>{if("string"==typeof e||Array.isArray(e))return e.length;throw new Error("length() expects string or array")}};function U(e){let t=0;const r=()=>e[t],n=()=>e[t++],o=(e,n)=>{const o=r();return!!o&&(o.type===e&&((void 0===n||o.value===n)&&(t++,!0)))},i=()=>{let e=null;if("Colon"!==r()?.type&&"Comma"!==r()?.type&&"ArrayEnd"!==r()?.type&&(e=h()),o("Colon")){let t=null;return"Comma"!==r()?.type&&"ArrayEnd"!==r()?.type&&(t=h()),{type:"SliceExpression",start:e,end:t}}return e};function a(){let e=function(){const e=n();if(!e)throw new Error("Unexpected end of input");switch(e.type){case"Number":case"BigInt":case"Boolean":case"String":return{type:"Literal",value:e.value};case"ImaginaryLiteral":return{type:"ImaginaryLiteral",value:e.value};case"NumberWithUnit":return{type:"UnitLiteral",value:e.value,unit:e.unit};case"Identifier":case"Function":return{type:"Identifier",name:e.name};case"Parenthesis":if("("===e.value){const e=h();if(!o("Parenthesis",")"))throw new Error("Expected ')'");return e}case"ArrayStart":{const e=[];let r=[];if(!o("ArrayEnd"))for(;;)if(r.push(h()),!o("Comma")){if(!o("Semicolon")){if(o("ArrayEnd")){e.push(r);break}throw new Error(`Expected ',', ';', or ']' at ${t}`)}e.push(r),r=[]}return e.length?1===e.length?{type:"ArrayExpression",elements:e[0]}:{type:"ArrayExpression",elements:e.map(e=>({type:"ArrayExpression",elements:e}))}:{type:"ArrayExpression",elements:[]}}case"BlockStart":{const e=[];if(!o("BlockEnd")){do{const t=n();if("Identifier"!==t.type&&"String"!==t.type)throw new Error("Invalid object key");if(!o("Colon"))throw new Error("Expected ':' after key");const r=h();e.push({key:t.value,value:r})}while(o("Comma"));if(!o("BlockEnd"))throw new Error(`Expected '}' at ${t}`)}return{type:"ObjectExpression",properties:e}}}throw new Error(`Unexpected token: ${JSON.stringify(e)}`)}();for(;;){if(o("ArrayStart")){const r=[];if(!o("ArrayEnd")){do{r.push(i())}while(o("Comma"));if(!o("ArrayEnd"))throw new Error(`Expected ']' at ${t}`)}e={type:"IndexExpression",object:e,selectors:r};continue}if(o("Dot")){const t=n();if("Identifier"!==t.type)throw new Error("Expected property after '.'");e={type:"MemberExpression",object:e,property:{type:"Identifier",name:t.value},optional:!1};continue}if(o("Operator","?.")){e={type:"MemberExpression",object:e,property:{type:"Identifier",name:n().value},optional:!0};continue}break}return e}function s(){if(o("UnaryOperator")){return{type:"UnaryExpression",operator:e[t-1].value,argument:s()}}return function(){let e=a();for(;"Parenthesis"===r()?.type&&"("===r()?.value;){n();const i=[];if("Parenthesis"!==r()?.type||")"!==r()?.value)do{i.push(h())}while(o("Comma"));if(!o("Parenthesis",")"))throw new Error(`Expected ')' at ${t}`);e={type:"CallExpression",callee:e,arguments:i}}return e}()}function l(){let e=s();if(o("Operator","^")){return{type:"BinaryExpression",operator:"^",left:e,right:l()}}return e}function u(){let r=l();for(;o("Operator","*")||o("Operator","/")||o("Operator","%");){r={type:"BinaryExpression",operator:e[t-1].value,left:r,right:l()}}return r}function c(){let i=function(){let r=u();for(;o("Operator","+")||o("Operator","-");)r={type:"BinaryExpression",operator:e[t-1].value,left:r,right:u()};return r}();const a=r();if("Keyword"===a?.type&&["to","in"].includes(a.value)){n();const e=n();if(!e||"Unit"!==e.type)throw new Error(`Expected unit after '${a.value}'`);return{type:"UnitConversion",from:i,to:e.value}}return i}function p(){let r=c();for(;o("Operator",">")||o("Operator","<")||o("Operator",">=")||o("Operator","<=")||o("Operator","==");){r={type:"BinaryExpression",operator:e[t-1].value,left:r,right:c()}}return r}function m(){let r=p();for(;o("Operator","&&")||o("Operator","||");){r={type:"LogicalExpression",operator:e[t-1].value,left:r,right:p()}}return r}function f(){let e=function(){let e=m();for(;o("Operator","??");)e={type:"LogicalExpression",operator:"??",left:e,right:m()};return e}();if(o("Ternary","?")){const t=h();if(!o("Ternary",":"))throw new Error("Expected ':' in ternary");return{type:"ConditionalExpression",test:e,consequent:t,alternate:h()}}return e}function y(){let r=function(){let e=f();for(;o("Operator","|>");)e={type:"PipelineExpression",left:e,right:f()};return e}();if(o("Operator","=")||o("Operator","+=")||o("Operator","-=")||o("Operator","*=")||o("Operator","/=")){const n=e[t-1].value;if("CallExpression"===r.type){if(!("Identifier"===r.callee?.type&&r.arguments.every(e=>"Identifier"===e.type)))throw new Error("Invalid function definition");const e=y();return{type:"FunctionAssignmentExpression",operator:n,left:{type:"Identifier",name:r.callee.name},params:r.arguments.map(e=>e.name),right:e}}if("Identifier"!==r.type&&"MemberExpression"!==r.type&&"IndexExpression"!==r.type)throw new Error("Invalid assignment target");return{type:"AssignmentExpression",operator:n,left:r,right:y()}}return r}function h(){return y()}const d=h();if(t<e.length)throw new Error(`Unexpected token at end: ${JSON.stringify(r())}`);return d}const C=e=>e&&"object"==typeof e&&"re"in e&&"im"in e,B=e=>"number"!=typeof e||Number.isInteger(e)?String(e):Number(e.toFixed(14)).toString(),I=t=>C(t)?(e=>{if(!C(e))return e;const t=e.re,r=Math.abs(e.im),n=e.im<0?"-":"+";return 0===t?1===e.im?"i":-1===e.im?"-i":`${e.im}i`:`${t} ${n} ${1===r?"i":`${r}i`}`})(t):(e=>e&&"object"==typeof e&&"value"in e&&"unit"in e)(t)?`${t.value} ${t.unit}`:e(t)?i(t):(e=>Array.isArray(e)&&e.length>0&&e.every(Array.isArray))(t)?t.map(e=>e.map(B).join("\t")).join("\n"):Array.isArray(t)?JSON.stringify(t):t&&"object"==typeof t?i(t):t;return class{constructor(){this.math=u,this.units=function(e={}){let t={...e};function r(e){e=e.toLowerCase();for(const r in t)for(const n in t[r]){const o=t[r][n];if(n.toLowerCase()===e||o.unit?.toLowerCase()===e||o.symbol?.toLowerCase()===e)return{type:r,key:n,data:o}}return null}return{getUnits:()=>t,setUnits:e=>{t={...e}},updateType:(e,r)=>{t[e]={...t[e],...r}},addUnit:(e,r,n)=>{t[e]||(t[e]={}),t[e][r]=n},compute(e,t,r){const n=e=>e&&"object"==typeof e&&"value"in e&&"unit"in e,o=(t,r)=>{switch(e){case"+":return t+r;case"-":return t-r;case"*":return t*r;case"/":return t/r;case"%":return t%r;case"^":return Math.pow(t,r)}};if(n(t)&&n(r)){const e=this.findUnit(r.unit),n=this.findUnit(t.unit);if(e.type!==n.type)throw new Error("Cannot operate on different unit types");const i=r.value*(e.data.value/n.data.value);return{value:o(t.value,i),unit:t.unit}}if(n(t)&&!n(r))return{value:o(t.value,r),unit:t.unit};if(!n(t)&&n(r)){return{value:o(t,r.value),unit:r.unit}}return o(t,r)},convert:function(e,n,o){const i=r(n),a=r(o);if(!i)throw new Error(`Unknown unit: ${n}`);if(!a)throw new Error(`Unknown unit: ${o}`);if(i.type!==a.type)throw new Error(`Cannot convert ${n} to ${o} (${a.data.unit||a.key}). ${i.data.unit||i.key} conversion units like ${Object.keys(t[i.type]).join(", ")}`);return{value:e*(i.data.value/a.data.value),unit:a.key}},getAllUnitsFlat:function(){const e=new Set;for(const r in t)for(const n in t[r]){const o=t[r][n],i=n.toLowerCase();if(e.add(i),o.unit){const t=o.unit.toLowerCase();t!==i&&1===t.split(/\s+/).length&&e.add(t)}if(o.symbol){const t=o.symbol.toLowerCase();o.unit&&t===o.unit.toLowerCase()||e.add(t)}}return Array.from(e)},findUnit:r}}(c),this.functions=function(e={}){const t=Object.create(null);for(const r in e)"function"==typeof e[r]&&(t[r]=e[r]);return{getAllFunctionsName:()=>Object.keys(t),register(e,r){if("string"!=typeof e||!e)throw new Error("Formula name must be a non-empty string");if("function"!=typeof r)throw new Error(`Formula "${e}" must be callable`);t[e]=r},get:e=>t[e],has:e=>Object.prototype.hasOwnProperty.call(t,e),remove(e){delete t[e]},all:()=>({...t}),clear(){for(const e in t)delete t[e]},extend(e={}){for(const r in e)"function"==typeof e[r]&&(t[r]=e[r])},clone:()=>createFormulaRegistry(t)}}(O),this.variables=m(),this._cache=new Map,this.variables.set("pi",Math.PI),this.variables.set("e",Math.E),this.addFunction("parse",e=>{if("string"!=typeof e)throw new Error("parse() expects an expression string");return e}),this.addFunction("leafCount",e=>{let t=e;if("string"==typeof e)try{t=this.parse(e).ast}catch{return(e=>{const t=e.replace(/(^|[{,]\s*)[a-zA-Z_][a-zA-Z0-9_]*\s*:/g,"$1").match(/\d+(\.\d+)?(e[+-]?\d+)?n?|[a-zA-Z_][a-zA-Z0-9_]*/gi);return t?t.length:0})(e)}const r=e=>{if(!e||"object"!=typeof e)return 0;switch(e.type){case"Literal":case"ImaginaryLiteral":case"UnitLiteral":case"Identifier":return 1;default:return Object.values(e).reduce((e,t)=>Array.isArray(t)?e+t.reduce((e,t)=>e+r(t),0):e+r(t),0)}};return r(t)}),this.addFunction("matrix",e=>n(e)),this.addFunction("sparse",e=>n(e)),this.addFunction("rationalize",(e,t=!1)=>{if("string"!=typeof e)throw new Error("rationalize() expects an expression string");const r=e.replace(/\s+/g,"").replace(/(\d)([a-zA-Z(])/g,"$1*$2").replace(/([a-zA-Z)])(\d)/g,"$1*$2"),n=e=>JSON.stringify(Object.entries(e).sort(([e],[t])=>e.localeCompare(t))),o=e=>Object.fromEntries(JSON.parse(e)),i=e=>new Map([[n({}),e]]),a=e=>new Map([...e.entries()].filter(([,e])=>0!==e)),s=(e,t,r=1)=>{const n=new Map(e);for(const[e,o]of t.entries())n.set(e,(n.get(e)||0)+r*o);return a(n)},l=(e,t)=>{const r=new Map;for(const[i,a]of e.entries()){const e=o(i);for(const[i,s]of t.entries()){const t=o(i),l={...e};for(const[e,r]of Object.entries(t))l[e]=(l[e]||0)+r;const u=n(l);r.set(u,(r.get(u)||0)+a*s)}}return a(r)},u=(e,t)=>{let r=i(1);for(let n=0;n<t;n++)r=l(r,e);return r},c=(e,t=i(1))=>({num:e,den:t}),p=(e,t,r=1)=>c(s(l(e.num,t.den),l(t.num,e.den),r),l(e.den,t.den)),m=e=>{switch(e.type){case"Literal":return c(i(e.value));case"Identifier":return c((a=e.name,new Map([[n({[a]:1}),1]])));case"UnaryExpression":if("-"===e.operator)return o=m(e.argument),c(s(new Map,o.num,-1),o.den);throw new Error("Unsupported unary operator");case"BinaryExpression":{const n=m(e.left),o=m(e.right);switch(e.operator){case"+":return p(n,o);case"-":return p(n,o,-1);case"*":return r=o,c(l((t=n).num,r.num),l(t.den,r.den));case"/":return((e,t)=>c(l(e.num,t.den),l(e.den,t.num)))(n,o);case"^":if("Literal"!==e.right.type||!Number.isInteger(e.right.value)||e.right.value<0)throw new Error("Unsupported exponent");return c(u(n.num,e.right.value),u(n.den,e.right.value));default:throw new Error("Unsupported operator in rationalize()")}}default:throw new Error("Unsupported expression in rationalize()")}var t,r,o,a},f=e=>{const t=[...e.entries()].filter(([,e])=>0!==e).sort(([e],[t])=>{const r=o(e),n=o(t),i=Object.keys(r).sort()[0]||"",a=Object.keys(n).sort()[0]||"";if(i!==a)return i.localeCompare(a);const s=Object.values(r).reduce((e,t)=>e+t,0);return Object.values(n).reduce((e,t)=>e+t,0)-s});return t.length?t.map(([e,t],r)=>{const n=o(e),i=Math.abs(t);let a=Object.entries(n).map(([e,t])=>1===t?e:`${e} ^ ${t}`).join(" * ");return a?1!==i&&(a=`${i} * ${a}`):a=`${i}`,0===r?t<0?`- ${a}`.replace("- ","-"):a:t<0?`- ${a}`:`+ ${a}`}).join(" "):"0"},y=this.parse(r).ast,h=m(y),d=f(h.num),v=f(h.den),g=new Set;for(const e of[h.num,h.den])for(const t of e.keys())for(const e of Object.keys(o(t)))g.add(e);return t?{numerator:d,denominator:v,coefficients:[],variables:[...g].sort(),expression:`(${d}) / (${v})`}:`(${d}) / (${v})`})}setVariable(e,t){this.variables.set(e,t)}getVariable(e){return this.variables.get(e)}addFunction(e,t){this.functions.register(e,t)}_createContext(){return s({functions:this.functions,variables:this.variables,units:this.units,evaluate:this.evaluate.bind(this)})}tokenize(e){if("string"!=typeof e)throw new Error("Expression must be a string");return function(e,t={}){const r=[];let n="",o="";const i=["+","-","*","/","%","^","=",">","<","!","&","|"],a=["==",">=","<=","&&","||","+=","-=","*=","/=","%=","?.","??","|>"],s=["to","in"],l=t.units?.getAllUnitsFlat?.()||[],u=e=>!e||"Operator"===e.type||"UnaryOperator"===e.type||"Parenthesis"===e.type&&")"!==e.value||"ArrayStart"===e.type||"Semicolon"===e.type||"Comma"===e.type||"Ternary"===e.type,c=(t,o)=>{if(!n)return;if(/^(true|false)$/i.test(n))return r.push({type:"Boolean",value:"true"===n.toLowerCase()}),void(n="");if(s.includes(n))return r.push({type:"Keyword",value:n,pos:o}),void(n="");if(/^\d+n$/.test(n))return r.push({type:"BigInt",value:BigInt(n.slice(0,-1)),pos:o}),void(n="");if(/^0x[0-9a-fA-F]+$/.test(n))return r.push({type:"Number",value:parseInt(n,16),pos:o}),void(n="");if(/^0b[01]+$/.test(n))return r.push({type:"Number",value:parseInt(n,2),pos:o}),void(n="");if(/^[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?$/i.test(n))return r.push({type:"Number",value:parseFloat(n),pos:o}),void(n="");if(/^[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?i$/i.test(n))return r.push({type:"ImaginaryLiteral",value:parseFloat(n.slice(0,-1)),pos:o}),void(n="");if(/^[+-]?i$/i.test(n)){const e="-"===n[0]?-1:1;return r.push({type:"ImaginaryLiteral",value:e,pos:o}),void(n="")}const i=n.match(/^([+-]?\d+(\.\d+)?)([a-zA-Z]+)$/);if(i){const e=parseFloat(i[1]),t=i[3];return r.push({type:l.includes(t)?"NumberWithUnit":"UnknownUnit",value:e,unit:t,pos:o}),void(n="")}if(l.includes(n)){const{prevWord:i}=function(e,t){const r=e.match(/[a-z0-9]+/gi)||[],n=e[t]||null,o=t>0?e[t-1]:null;let i=t;for(;i>0&&/[a-z0-9]/i.test(e[i-1]);)i--;let a=t;for(;a<e.length&&/[a-z0-9]/i.test(e[a]);)a++;const s=e.substring(i,a),l=r.indexOf(s);return{prevWord:l>0?r[l-1]:null,prevChar:o,currentWord:s,currentChar:n,nextWord:-1!==l&&l<r.length-1?r[l+1]:null}}(e,o);if("("!==t&&i&&(!isNaN(parseFloat(i))||"to"===i||"in"===i))return r.push({type:"Unit",value:n,pos:o}),void(n="")}if(/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(n))return"("===t?r.push({type:"Function",name:n,pos:o}):r.push({type:"Identifier",name:n,pos:o}),void(n="");throw new Error(`Invalid token "${n}" at index ${o}`)};for(let t=0;t<e.length;t++){let s=e[t],l=e[t+1];if("/"===s&&"/"===l){for(;t<e.length&&"\n"!==e[t];)t++;continue}if("/"===s&&"*"===l){for(t+=2;t<e.length&&("*"!==e[t]||"/"!==e[t+1]);)t++;t++;continue}if("\"'".includes(s)){o?o===s?(n+=s,r.push({type:"String",value:n.slice(1,-1),pos:t}),n="",o=""):n+=s:(o=s,n+=s);continue}if(o){n+="\\"===s?s+e[++t]:s;continue}const p=s+l;if(a.includes(p))c(s,t),r.push({type:"Operator",value:p,pos:t}),t++;else if("?"!==s){if(":"===s){c(s,t);const e=r[r.length-1];e&&"Ternary"===e.type?r.push({type:"Ternary",value:":"}):r.push({type:"Colon"});continue}if("."===s&&/\d/.test(n)&&/\d/.test(l))n+=s;else if("."!==s){if(i.includes(s)){c(s,t);const e=r[r.length-1];"-"!==s&&"!"!==s||!u(e)?r.push({type:"Operator",value:s,pos:t}):r.push({type:"UnaryOperator",value:s,pos:t});continue}"()".includes(s)?(c(s,t),r.push({type:"Parenthesis",value:s,pos:t})):"["!==s?"]"!==s?"{"!==s?"}"!==s?","!==s?";"!==s?" "!==s?(n+=s,t===e.length-1&&c(null,t)):c(l,t):(c(s,t),r.push({type:"Semicolon",pos:t})):(c(s,t),r.push({type:"Comma",pos:t})):(c(s,t),r.push({type:"BlockEnd",pos:t})):(c(s,t),r.push({type:"BlockStart",pos:t})):(c(s,t),r.push({type:"ArrayEnd",pos:t})):(c(s,t),r.push({type:"ArrayStart",pos:t}))}else c(s,t),r.push({type:"Dot",pos:t})}else r.push({type:"Ternary",value:"?"})}if(o)throw new Error("Unclosed string literal");const p=[];for(let e=0;e<r.length;e++){const t=r[e],n=r[e+1];"Number"!==t?.type||"Unit"!==n?.type?p.push(t):(p.push({type:"NumberWithUnit",value:t.value,unit:n.value,pos:t.pos}),e++)}const m=[];for(let e=0;e<p.length;e++){const t=p[e],r=p[e+1];m.push(t),t&&r&&(["Number","Identifier"].includes(t.type)||"Parenthesis"===t.type&&")"===t.value||"ArrayEnd"===t.type)&&(["Identifier","Function"].includes(r.type)||"Parenthesis"===r.type&&"("===r.value)&&m.push({type:"Operator",value:"*",implicit:!0})}return m}(e,this._createContext())}parse(e){const t=this.tokenize(e);return{tokens:t,ast:U(t)}}evaluate(e){const{ast:t}=this.parse(e);return I(a(t,this._createContext()))}compile(e){if(this._cache.has(e))return this._cache.get(e);const{ast:t}=this.parse(e),r=(e={})=>{const r=this._createContext().withScope(e);return I(a(t,r))};return this._cache.set(e,r),r}clearCache(){this._cache.clear()}}});
3
3
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exprify",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A powerful math expression parser and evaluator with runtime data-type checking",
5
5
  "type": "module",
6
6
  "main": "dist/exprify.cjs.js",
Binary file
@@ -0,0 +1,369 @@
1
+ import { tokenize } from "../parser/tokenizer.js";
2
+ // import { infixToPostfix } from "../parser/infixToPostfix.js";
3
+ import { evaluateAST } from "../parser/evaluator.js";
4
+ import { createContext } from "./context.js";
5
+ import { mathOperations } from "../math/operations.js";
6
+
7
+ import { createUnitsStore } from "../utils/store.js";
8
+ import { globalUnits } from "../utils/globalUnits.js";
9
+
10
+ import { createVarStore } from "../variables/store.js";
11
+ import { createFunctionRegistry } from "../function/registry.js";
12
+ import { internalFunctions } from "../function/internal.js";
13
+ import { isDenseMatrixWrapper, serializeExprifyValue, wrapDenseMatrix } from "../utils/matrix.js";
14
+
15
+ import { buildAST } from "../parser/astBuild.js";
16
+
17
+
18
+ //
19
+
20
+ const isComplex = (value) =>
21
+ value && typeof value === "object" && "re" in value && "im" in value;
22
+
23
+ const isUnitValue = (value) =>
24
+ value && typeof value === "object" && "value" in value && "unit" in value;
25
+
26
+ const isMatrix = (value) =>
27
+ Array.isArray(value) && value.length > 0 && value.every(Array.isArray);
28
+
29
+ const formatComplex = (value) => {
30
+ if (!isComplex(value)) return value;
31
+
32
+ const real = value.re;
33
+ const imaginary = Math.abs(value.im);
34
+ const sign = value.im < 0 ? "-" : "+";
35
+
36
+ if (real === 0) {
37
+ if (value.im === 1) return "i";
38
+ if (value.im === -1) return "-i";
39
+ return `${value.im}i`;
40
+ }
41
+
42
+ const imagPart = imaginary === 1 ? "i" : `${imaginary}i`;
43
+ return `${real} ${sign} ${imagPart}`;
44
+ };
45
+
46
+ const formatScalar = (value) => {
47
+ if (typeof value !== "number") {
48
+ return String(value);
49
+ }
50
+
51
+ if (Number.isInteger(value)) {
52
+ return String(value);
53
+ }
54
+
55
+ return Number(value.toFixed(14)).toString();
56
+ };
57
+
58
+ const formatResult = (value) => {
59
+ if (isComplex(value)) {
60
+ return formatComplex(value);
61
+ }
62
+
63
+ if (isUnitValue(value)) {
64
+ return `${value.value} ${value.unit}`;
65
+ }
66
+
67
+ if (isDenseMatrixWrapper(value)) {
68
+ return serializeExprifyValue(value);
69
+ }
70
+
71
+ if (isMatrix(value)) {
72
+ return value.map((row) => row.map(formatScalar).join("\t")).join("\n");
73
+ }
74
+
75
+ if (Array.isArray(value)) {
76
+ return JSON.stringify(value);
77
+ }
78
+
79
+ if (value && typeof value === "object") {
80
+ return serializeExprifyValue(value);
81
+ }
82
+
83
+ return value;
84
+ };
85
+
86
+ class exprify {
87
+ constructor() {
88
+ // Shared state
89
+ this.math = mathOperations;
90
+ this.units = createUnitsStore(globalUnits);
91
+ this.functions = createFunctionRegistry(internalFunctions);
92
+ this.variables = createVarStore();
93
+ this._cache = new Map();
94
+ this.variables.set("pi", Math.PI);
95
+ this.variables.set("e", Math.E);
96
+ this.addFunction("parse", (expression) => {
97
+ if (typeof expression !== "string") {
98
+ throw new Error("parse() expects an expression string");
99
+ }
100
+ return expression;
101
+ });
102
+ this.addFunction("leafCount", (value) => {
103
+ const countLeafTokens = (expression) => {
104
+ const strippedKeys = expression.replace(/(^|[{,]\s*)[a-zA-Z_][a-zA-Z0-9_]*\s*:/g, "$1");
105
+ const matches = strippedKeys.match(/\d+(\.\d+)?(e[+-]?\d+)?n?|[a-zA-Z_][a-zA-Z0-9_]*/gi);
106
+ return matches ? matches.length : 0;
107
+ };
108
+
109
+ let ast = value;
110
+ if (typeof value === "string") {
111
+ try {
112
+ ast = this.parse(value).ast;
113
+ } catch {
114
+ return countLeafTokens(value);
115
+ }
116
+ }
117
+
118
+ const countLeaves = (node) => {
119
+ if (!node || typeof node !== "object") return 0;
120
+
121
+ switch (node.type) {
122
+ case "Literal":
123
+ case "ImaginaryLiteral":
124
+ case "UnitLiteral":
125
+ case "Identifier":
126
+ return 1;
127
+ default:
128
+ return Object.values(node).reduce((sum, child) => {
129
+ if (Array.isArray(child)) {
130
+ return sum + child.reduce((inner, item) => inner + countLeaves(item), 0);
131
+ }
132
+
133
+ return sum + countLeaves(child);
134
+ }, 0);
135
+ }
136
+ };
137
+
138
+ return countLeaves(ast);
139
+ });
140
+ this.addFunction("matrix", (value) => wrapDenseMatrix(value));
141
+ this.addFunction("sparse", (value) => wrapDenseMatrix(value));
142
+ this.addFunction("rationalize", (expression, withDetails = false) => {
143
+ if (typeof expression !== "string") {
144
+ throw new Error("rationalize() expects an expression string");
145
+ }
146
+
147
+ const normalizedExpression = expression
148
+ .replace(/\s+/g, "")
149
+ .replace(/(\d)([a-zA-Z(])/g, "$1*$2")
150
+ .replace(/([a-zA-Z)])(\d)/g, "$1*$2");
151
+
152
+ const polyKey = (powers) => JSON.stringify(Object.entries(powers).sort(([a], [b]) => a.localeCompare(b)));
153
+ const keyToPowers = (key) => Object.fromEntries(JSON.parse(key));
154
+ const makePoly = (terms = new Map()) => terms;
155
+ const constPoly = (value) => new Map([[polyKey({}), value]]);
156
+ const varPoly = (name) => new Map([[polyKey({ [name]: 1 }), 1]]);
157
+ const cleanPoly = (poly) => new Map([...poly.entries()].filter(([, coeff]) => coeff !== 0));
158
+ const addPoly = (a, b, sign = 1) => {
159
+ const result = new Map(a);
160
+ for (const [key, coeff] of b.entries()) {
161
+ result.set(key, (result.get(key) || 0) + (sign * coeff));
162
+ }
163
+ return cleanPoly(result);
164
+ };
165
+ const multiplyPoly = (a, b) => {
166
+ const result = new Map();
167
+ for (const [keyA, coeffA] of a.entries()) {
168
+ const powersA = keyToPowers(keyA);
169
+ for (const [keyB, coeffB] of b.entries()) {
170
+ const powersB = keyToPowers(keyB);
171
+ const merged = { ...powersA };
172
+ for (const [name, power] of Object.entries(powersB)) {
173
+ merged[name] = (merged[name] || 0) + power;
174
+ }
175
+ const key = polyKey(merged);
176
+ result.set(key, (result.get(key) || 0) + (coeffA * coeffB));
177
+ }
178
+ }
179
+ return cleanPoly(result);
180
+ };
181
+ const powPoly = (poly, exponent) => {
182
+ let result = constPoly(1);
183
+ for (let index = 0; index < exponent; index++) {
184
+ result = multiplyPoly(result, poly);
185
+ }
186
+ return result;
187
+ };
188
+ const rational = (num, den = constPoly(1)) => ({ num, den });
189
+ const addRat = (a, b, sign = 1) => rational(
190
+ addPoly(
191
+ multiplyPoly(a.num, b.den),
192
+ multiplyPoly(b.num, a.den),
193
+ sign
194
+ ),
195
+ multiplyPoly(a.den, b.den)
196
+ );
197
+ const mulRat = (a, b) => rational(multiplyPoly(a.num, b.num), multiplyPoly(a.den, b.den));
198
+ const divRat = (a, b) => rational(multiplyPoly(a.num, b.den), multiplyPoly(a.den, b.num));
199
+ const negRat = (value) => rational(addPoly(new Map(), value.num, -1), value.den);
200
+ const astToRat = (node) => {
201
+ switch (node.type) {
202
+ case "Literal":
203
+ return rational(constPoly(node.value));
204
+ case "Identifier":
205
+ return rational(varPoly(node.name));
206
+ case "UnaryExpression":
207
+ if (node.operator === "-") return negRat(astToRat(node.argument));
208
+ throw new Error("Unsupported unary operator");
209
+ case "BinaryExpression": {
210
+ const left = astToRat(node.left);
211
+ const right = astToRat(node.right);
212
+ switch (node.operator) {
213
+ case "+": return addRat(left, right);
214
+ case "-": return addRat(left, right, -1);
215
+ case "*": return mulRat(left, right);
216
+ case "/": return divRat(left, right);
217
+ case "^": {
218
+ if (node.right.type !== "Literal" || !Number.isInteger(node.right.value) || node.right.value < 0) {
219
+ throw new Error("Unsupported exponent");
220
+ }
221
+ return rational(
222
+ powPoly(left.num, node.right.value),
223
+ powPoly(left.den, node.right.value)
224
+ );
225
+ }
226
+ default:
227
+ throw new Error("Unsupported operator in rationalize()");
228
+ }
229
+ }
230
+ default:
231
+ throw new Error("Unsupported expression in rationalize()");
232
+ }
233
+ };
234
+ const formatPoly = (poly) => {
235
+ const entries = [...poly.entries()]
236
+ .filter(([, coeff]) => coeff !== 0)
237
+ .sort(([keyA], [keyB]) => {
238
+ const powersA = keyToPowers(keyA);
239
+ const powersB = keyToPowers(keyB);
240
+ const firstVarA = Object.keys(powersA).sort()[0] || "";
241
+ const firstVarB = Object.keys(powersB).sort()[0] || "";
242
+
243
+ if (firstVarA !== firstVarB) {
244
+ return firstVarA.localeCompare(firstVarB);
245
+ }
246
+
247
+ const degreeA = Object.values(powersA).reduce((sum, value) => sum + value, 0);
248
+ const degreeB = Object.values(powersB).reduce((sum, value) => sum + value, 0);
249
+ return degreeB - degreeA;
250
+ });
251
+
252
+ if (!entries.length) return "0";
253
+
254
+ return entries.map(([key, coeff], index) => {
255
+ const powers = keyToPowers(key);
256
+ const absCoeff = Math.abs(coeff);
257
+ const variablePart = Object.entries(powers)
258
+ .map(([name, power]) => power === 1 ? name : `${name} ^ ${power}`)
259
+ .join(" * ");
260
+ let body = variablePart;
261
+
262
+ if (!body) {
263
+ body = `${absCoeff}`;
264
+ } else if (absCoeff !== 1) {
265
+ body = `${absCoeff} * ${body}`;
266
+ }
267
+
268
+ if (index === 0) {
269
+ return coeff < 0 ? `- ${body}`.replace("- ", "-") : body;
270
+ }
271
+
272
+ return coeff < 0 ? `- ${body}` : `+ ${body}`;
273
+ }).join(" ");
274
+ };
275
+
276
+ const ast = this.parse(normalizedExpression).ast;
277
+ const result = astToRat(ast);
278
+ const numerator = formatPoly(result.num);
279
+ const denominator = formatPoly(result.den);
280
+ const variableSet = new Set();
281
+
282
+ for (const poly of [result.num, result.den]) {
283
+ for (const key of poly.keys()) {
284
+ for (const name of Object.keys(keyToPowers(key))) {
285
+ variableSet.add(name);
286
+ }
287
+ }
288
+ }
289
+
290
+ if (!withDetails) {
291
+ return `(${numerator}) / (${denominator})`;
292
+ }
293
+
294
+ return {
295
+ numerator,
296
+ denominator,
297
+ coefficients: [],
298
+ variables: [...variableSet].sort(),
299
+ expression: `(${numerator}) / (${denominator})`
300
+ };
301
+ });
302
+ }
303
+
304
+ setVariable(name, value) {
305
+ this.variables.set(name, value);
306
+ }
307
+
308
+ getVariable(name) {
309
+ return this.variables.get(name);
310
+ }
311
+
312
+ addFunction(name, fn) {
313
+ this.functions.register(name, fn);
314
+ }
315
+
316
+ _createContext() {
317
+ return createContext({
318
+ functions: this.functions,
319
+ variables: this.variables,
320
+ units: this.units,
321
+ evaluate: this.evaluate.bind(this)
322
+ });
323
+ }
324
+
325
+ tokenize(expr) {
326
+ if (typeof expr !== "string") {
327
+ throw new Error("Expression must be a string");
328
+ }
329
+ return tokenize(expr, this._createContext());
330
+ }
331
+
332
+ parse(expr) {
333
+ const tokens = this.tokenize(expr);
334
+ const ast = buildAST(tokens);
335
+ return { tokens, ast };
336
+ }
337
+
338
+ evaluate(expr) {
339
+ const { ast } = this.parse(expr);
340
+ return formatResult(evaluateAST(
341
+ ast,
342
+ this._createContext()
343
+ ));
344
+ }
345
+
346
+ compile(expr) {
347
+ if (this._cache.has(expr)) {
348
+ return this._cache.get(expr);
349
+ }
350
+
351
+ const { ast } = this.parse(expr);
352
+
353
+ const compiledFn = (scope = {}) => {
354
+ const baseContext = this._createContext();
355
+ const scopedContext = baseContext.withScope(scope);
356
+ return formatResult(evaluateAST(ast, scopedContext));
357
+ };
358
+
359
+ this._cache.set(expr, compiledFn);
360
+ return compiledFn;
361
+ }
362
+
363
+ clearCache() {
364
+ this._cache.clear();
365
+ }
366
+
367
+ }
368
+
369
+ export default exprify;
@@ -0,0 +1,30 @@
1
+ export function createContext({ variables, functions, units, evaluate}) {
2
+ if (!variables) throw new Error("Variable store missing");
3
+ if (!functions) throw new Error("Function registry missing");
4
+ if (!units) throw new Error("Units list missing");
5
+ if (!evaluate) throw new Error("evaluate function missing");
6
+
7
+ return {
8
+ variables: variables,
9
+ functions: functions,
10
+ units: units,
11
+ evaluate,
12
+ withScope(scope = {}) {
13
+ const tempVars = {
14
+ ...variables.all?.(),
15
+ ...scope
16
+ };
17
+ return createContext({
18
+ functions: functions,
19
+ evaluate,
20
+ units,
21
+ variables: {
22
+ get: (k) => tempVars[k],
23
+ set: (k, v) => (tempVars[k] = v),
24
+ all: () => tempVars
25
+ }
26
+ });
27
+
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,64 @@
1
+ export function createFunctionExecutor(fnRegistry, options = {}) {
2
+ if (!fnRegistry) {
3
+ throw new Error("Function registry is required");
4
+ }
5
+
6
+ const config = {
7
+ strict: options.strict ?? true
8
+ };
9
+
10
+ /* ================= EXECUTE ================= */
11
+
12
+ function execute(name, args = [], context) {
13
+ const fn = fnRegistry.get(name);
14
+
15
+ /* ----- NOT FOUND ----- */
16
+ if (!fn) {
17
+ if (config.strict) {
18
+ throw new Error(`Unknown function: ${name}`);
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ /* ----- VALIDATE ARGS ----- */
24
+ if (!Array.isArray(args)) {
25
+ throw new Error(`Arguments for "${name}" must be an array`);
26
+ }
27
+
28
+ /* ----- EXECUTE ----- */
29
+ try {
30
+ return fn(...args);
31
+ } catch (err) {
32
+ throw new Error(
33
+ `Error in function "${name}": ${err.message}`
34
+ );
35
+ }
36
+ }
37
+
38
+ /* ================= SAFE EXECUTE ================= */
39
+
40
+ function safeExecute(name, args = [], context) {
41
+ try {
42
+ return execute(name, args, context);
43
+ } catch (err) {
44
+ return {
45
+ error: true,
46
+ message: err.message
47
+ };
48
+ }
49
+ }
50
+
51
+ /* ================= EXISTS ================= */
52
+
53
+ function exists(name) {
54
+ return fnRegistry.has(name);
55
+ }
56
+
57
+ /* ================= API ================= */
58
+
59
+ return {
60
+ execute,
61
+ safeExecute,
62
+ exists
63
+ };
64
+ }