Graphite icon indicating copy to clipboard operation
Graphite copied to clipboard

Tracking Issue: Fully-featured math expression parser and calculator

Open Keavon opened this issue 1 year ago • 5 comments

This should be built as a library (in our /libraries directory) that uses a parsing framework such as Chumsky that takes a string at runtime and calculates its result, including units and dimensional analysis.

This would make a great solo contribution for somebody interested in developing a robust solution for this problem without needing to learn any of Graphite's code base. It would also make a good university team/solo "capstone" project, which our organization can serve as an "industry sponsor" for.

Here's the spec:

Operators

  • [ ] Infix operators:
    • [ ] Addition: +
    • [ ] Subtraction: -
    • [ ] Multiplication: *
    • [ ] Division: /
    • [ ] Modulo: %
    • [ ] Exponentiation: ^
    • [ ] Equals: ==
    • [ ] Not Equals: !=
    • [ ] Less Than or Equal To: <=,
    • [ ] Greater Than or Equal To: >=,
    • [ ] Less Than: <
    • [ ] Greater Than: >
    • [ ] Or: ||
    • [ ] And: &&
  • [ ] Prefix operators:
    • [ ] Unary Plus: +
    • [ ] Unary Minus: -
    • [ ] Square root:
    • [ ] Not:!
  • [ ] Postfix operators:
    • [ ] Factorial: !
  • [ ] Grouping operators:
    • [ ] Parentheses: ( and )
  • [ ] Logical operators:
    • [ ] Equality: ==, !=, >=, <=, <, >, ||, &&, and ! logical operators to the spec.

Constants

  • [ ] Infinity: inf, INF, infinity, INFINITY,
  • [ ] Imaginary unit: i, I
  • [ ] Pi: pi, PI, π
  • [ ] Tau: tau, TAU, τ
  • [ ] Euler's Number: e
  • [ ] Golden Ratio: phi, PHI, φ
  • [ ] Earth's gravitational acceleration: G

Variables

  • [ ] Single-letter variables (examples: x, y, z)
  • [ ] Multi-letter variables (examples: theta, alpha, beta, gamma)
  • [ ] Non-Latin letters and symbols (examples: λ, , א, 👍)

Functions

  • [ ] Trig:
    • [ ] sin(), cos(), tan()
    • [ ] csc(), sec(), cot()
  • [ ] Inverse trig (aliases: ar- and arc- prefixes):
    • [ ] asin(), acos(), atan(), atan2()
    • [ ] acsc(), asec(), acot()
  • [ ] Hyperbolic:
    • [ ] sinh(), cosh(), tanh()
    • [ ] csch(), sech(), coth()
  • [ ] Inverse hyperbolic (aliases: ar- and arc- prefixes):
    • [ ] asinh(), acosh(), atanh()
    • [ ] acsch(), asech(), acoth()
  • [ ] Logarithm:
    • [ ] Natural log: ln()
    • [ ] Logarithm base 10: log()
    • [ ] Logarithm base 2: log2()
    • [ ] Logarithm base N: logN() (alias: log_N())
      • Examples: log5(), log3.25(), log_8()
    • [ ] Logarithm with variable base: log(x, b)
  • [ ] Exponential:
    • [ ] e^x: exp()
    • [ ] 2^x: exp2()
  • [ ] Roots:
    • [ ] Square root: sqrt() (alias: root(), root2(), root_2())
    • [ ] Cube root: cbrt() (alias: root3(), root_3())
    • [ ] Nth root: rootN() (alias: root_N())
      • Examples: root3(), root5.237(), root_27.72()
    • [ ] Root with variable degree: root(n, x)
      • Example: root(27, 3) is equivalent to cbrt(27)
  • [ ] Geometry:
    • [ ] Hypotenuse equation sqrt(a^2 + b^2): hypot()
  • [ ] Mapping:
    • [ ] Absolute value: abs() (magnitude of a real or complex number)
    • [ ] Floor: floor()
    • [ ] Ceiling: ceil()
    • [ ] Round: round()
    • [ ] Clamp: clamp(x, min, max)
    • [ ] Lerp: lerp(a, b, t)
    • [ ] Truncate: trunc()
    • [ ] Fractional part: frac()
    • [ ] Sign: sign()
  • [ ] Logical
    • [ ] Is NaN: isnan() (returns 1 if the operand is NaN, 0 otherwise)
    • [ ] Equality: eq() (returns 1 if both operands and units are equal, 0 otherwise)
    • [ ] Greater than: greater() (returns 1 if the left operand is greater than the right operand and units are equal, 0 otherwise)
    • [ ] Greater-or-equal: greatereq() (returns 1 if the left operand is greater than or equal to the right operand and units are equal, 0 otherwise)
    • [ ] Less than: lesser() (returns 1 if the left operand is less than the right operand and units are equal, 0 otherwise)
    • [ ] Less-or-equal: lessereq() (returns 1 if the left operand is less than or equal to the right operand and units are equal, 0 otherwise)
    • [ ] Not: not() (returns 1 if the operand is zero, 0 otherwise)
    • [ ] And: and() (returns 1 if both operands are non-zero, 0 otherwise)
    • [ ] Or: or() (returns 1 if either operand is non-zero, 0 otherwise)
    • [ ] Xor: xor() (returns 1 if exactly one operand is non-zero, 0 otherwise)
    • [ ] If: if(cond, a, b) (returns a if cond is non-zero, b otherwise)
      • A potential alternative to nonzeroness in these logical functions could be using true and false as units with either no value, a value of 0, or a value of 1
  • [ ] Complex numbers:
    • [ ] Real part: real()
    • [ ] Imaginary part: imag()
    • [ ] Angle: arg() (alias: angle())

Number representations

  • [ ] Integers (examples: 0, 42, -42)
  • [ ] Decimals (examples: 0.5, 3.14159, -2.71828)
  • [ ] Scientific notation (examples: 1e-6, 2.5E3)
  • [ ] Imaginary numbers (examples: 3i, -2.5i, 1.5e-3i, 2.5e3 + 2.1e-2i)
  • [ ] Units (examples: 5m, 5 m, 3.5kg, 3.5 kg, 2.5m/s^2, 2.5 m/s^2)

Units

  • In addition to the unit abbreviations, full unit names can be used in either singular or plural form
  • Math expressions perform dimensional analysis
    • The scalar part is simplified while the unit part is kept as a fraction if it cannot be simplified
    • Units should only be combined if they are mixed with other units in either the numerator or denominator, defaulting to the unit that would best avoid loss of precision
      • Example: 5m + 3m -> 8m
      • Example: 5ft + 3ft -> 8ft
      • Example: 5m + 3ft -> 5.9144m (meters are chosen because 1ft = 0.3048m exactly, while its inverse can't be represented exactly)
  • Values can always be requested for conversion to a specific unit in the API

Unit list

  • Unitless
  • Pixels:
    • px
  • Length:
    • Meter m and its prefixes
    • thou, pc, pt, in/", ft/', yd, mi, nmi
  • Area:
    • length^2
    • acre, are, hectare
  • Volume:
    • length^3, area * length
    • Paper sizes: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10
    • floz, cup, pint, qt, gal
    • l and its prefixes
  • Mass:
    • Kilogram kg and its prefixes
    • gr, oz, lb, ton, tonne
  • Time:
    • Second s and its prefixes
    • min, hr, day
  • Angle:
    • Degrees: deg/°
    • Radians: rad (unitless is not an angle)
    • Turns: turn
  • Velocity:
    • length / time
    • m/s, km/h, mph, knot
  • Acceleration:
    • velocity / time, length / time^2
    • m/s^2, ft/s^2, g
  • Angular velocity:
    • angle / time
    • deg/s, rad/s, rpm, rps
  • Angular acceleration:
    • angular velocity / time, angle / time^2
    • deg/s^2, rad/s^2
  • Illumination
    • TODO: Research and lay out what these are, like candela, nits, etc.

Unit metric prefixes

  • Nano (n)
  • Micro (µ, u)
  • Milli (m)
  • Centi (c)
  • Deci (d)
  • Deca (da)
  • Hecto (h)
  • Kilo (k)
  • Mega (M)
  • Giga (G)
  • Tera (T)

Implicit multiplication

  • [ ] Automatically handle multiplication without explicit * operator
    • [ ] Between numbers and variables/constants (examples: 3x, 2pi)
    • [ ] Between numbers and functions (example: 4sin(90deg))
    • [ ] Between adjacent variables/constants (examples: xy, pi r^2)

API features

  • [ ] Parsing/construction separate from evaluation so the same expression can be run repeatedly with different variables without wasted performance
  • [ ] Setting the result of one expression to a variable for use in subsequent expressions
  • [ ] Custom supplied units, unit symbols, functions, and constants
  • [ ] Syntax highlighting

  • [x] Initial library implementation (#2033)
  • [ ] Add more features as described in the spec above, such as abs() and others

Keavon avatar Oct 09 '24 07:10 Keavon

I'm interested in this issue. I'm a bit new, but I hope to do my best and make progress in this field.

abueno19 avatar Oct 09 '24 14:10 abueno19

@abueno19 that would be great, thanks for volunteering! We'd ideally like to get a reasonably functional version of this working in the next month or so, with progress towards the full functionality outlined here over the next couple months. My plan was to post this issue on /r/rust to find interested collaborators. Multiple people could probably work on it together, like writing tests, figuring out the correct formal grammar, handling the AST, working with the dimensional analysis, calculating the values, implementing the functions, etc. What's your level of experience with software engineering or computer science in general, and grammar parsing in particular, and would you like to work with others on this?

Keavon avatar Oct 09 '24 18:10 Keavon

@Keavon

Hello,
I have 2 years of experience working with IoT in .NET, and I’ve been working with Rust for 3 months on my own mini-projects.

As for grammar analysis, I don’t think I’d have any issues with parsing.

Regarding your question about working with others, I would love to!

What I’m looking to gain from this is learning how to structure code, work in a community, and learn new methodologies.

Just let me know, and I’ll start with the basics (character parsing system).
By the way, how will the data be passed around?

abueno19 avatar Oct 10 '24 13:10 abueno19

This issue seems interesting, I'm working on a parser for desmos expressions and the syntax is very similar, so maybe some of the code can be reused.

urisinger avatar Oct 10 '24 22:10 urisinger

Thanks for the introduction, @abueno19! Could you join our Discord (https://discord.graphite.rs) and work with @urisinger in the new channel I created for this library project, which I've tentatively called #🧮math-expr-calc? Easier to communicate there by chatting compared to here. See you there!

Keavon avatar Oct 12 '24 20:10 Keavon