Mathparser is a mathematical expression language based on C-style syntax. It was created out of a desire for a simple mathematical script language outside of spreadsheets, online c-compilers, mathlab and other math tools. I think it may feel more familiar to programmers than those other tools. Mathparser is usefull for simple arithmetic and some trigonometry. Don't expect much more than that.
Mathparser is also a sandbox to try out some language design ideas. For example, the units notation and repl style immediate output or formatted output or muting output,....
//this is a comment line
a=3+4; //this is a typical statement.
b=2a+3; //implicit multiplication is allowed: same as (2*a)+3;
a+=2; b=a*10; //a statement always ends with a semi-colon, not a new line, so these are two statements.
randomNumbers = 1, 234, 567; // a list of numbers (array)
A dot is the decimal separator, but in the section Formatted values there's an alternative.
123.456;
0xFF; //hexadecimal notation for decimal 255
0b101; //binary notation for decimal 5
123e4; //scientific notation for decimal 1230000
The type of notation for a variable is remembered for as long as possible. That is, MathParser will try to output the value in the initial format.
In addition to the usual + - * /
operators, there are:
a+=7; //same as a=a+7, also works with - * /
a=3^2; //exponent operator
a=|-3|; //absolute value operator
a++; //increment operator: same as a=a+1 or a+=1;
a--; //decrement operator.
a%3; //remainder operator: -15%12 = -3;
a%%3; //modulus operator: -15%%12 = 9; //very usefull for dates and hours: starting from midnight, -15 hours = 9:00 , not -3:00 !
3!; //factorials.
a=2*7; //will output the variable and it's value: a=14
7*3; //will output the value: 21
An exclamation mark !
is used to echo the full expression to the output.
!b=60/3; //Will echo the code together with the result, but not the comment --> b=20 b=60/3
!//this comment line will appear on the output
!q=100/10; !//some comment --> output: q=10 q=100/10 //some comment
!!q=100/10; //some comment --> echo the full line (same effect as the line above)
!/// --> echo all the input for all the lines until...
///! --> end of echo
A hashtag #
is used to mute the result.
#c=sin(30deg); //suppress output, but still execute the code.
/# //suppress output for the next 4 lines
a+=1;
a+=3;
a+=PI;
a+=7;
#/
a; //finally, the result will be output
//alternatively, you can comment a block of code::
#{
a+=1;
a+=3;
a+=PI;
a+=7;
}
//...or only mute the content of the block, not the final result:
{
/#
a+=1;
a+=3;
a+=PI;
a+=7;
// note that the closing `#/` is optional and is implied at the end of the block.
}
Units can be appended either directly to numbers or seperated by a dot for identifiers. Values get automatically converted to the last unit.
12kg;
twelve.kg;
Distance=10km+1mi; //Distance will be expressed in the first unit: km.
//Conversion
Distance=100; //Standard unit (m) implied.
Imperial=Distance.mi; //Conversion from meter to miles.
Result=(Imperial+5m).km; //Conversion of an expression.
Hot=1000K.C.F; //Conversion from K to C to F. Note that the intermediate conversion to C is pointless.
//Only output or conversion?
Hot=1000K;
Hot.C; //Output the value of Hot in Celcius. The variable Hot remains in K!
Hot=Hot.C; //Convert Hot to Celcius.
Hot.=C; //Does the same as the above line.
- Angle:
rad, deg
- Length:
km, m, cm, mm, um (micron), in, ft, mi, thou, yd
- Temperature:
C, F, K
- Mass (weight):
kg, g, mg, t, lb (lbs), oz, N
(note that for convenience no distinction is made between weight and mass) - Volume:
L, ml, gal, pt
Like units, an output format can be specified with dot notation.
123.dec; // 123 (default)
123.hex; // 0x7B
123.oct; // 0o173
123.bin; // 0b1111011
123.exp; // 123e0
- Trigonometry:
sin, cos, tan, asin, acos, atan
- Other:
round, floor, ceil, trunc, abs, factors
max(randomNumbers); // lists (arrays) can also be used as arguments
|x|
is the same asabs(x)
- Dates:
now(), date(year, month, day)
- Lists:
sort(), reverse(), max(), min(), avg(), flatten(), first(), last()
Theflatten()
function flattens nested lists:(1, ((2,3), 4, 5), 6)
->(1,2,3,4,5,6)
Statements can be grouped in functions as well.
x = 100;
function hundred(a)
{
cent = x;//error: variables from outside the function are not visible within the function body (scope).
cent = 100; //ok
a*cent; //the last statement defines the return value of the function.
}
cent++;//error: cent was a local variable of hundred(). It's out of scope here.
x=hundred(1+2); // = 300
Currently only PI. (also in lower case) [TODO]: G, C, e...
Formatted values are values between single quotes that are guessed what they may mean and allow for different locales.
a='01/jan/2022'; //interpreted as a date.
#define decimal_comma //use a comma as a decimal point and a dot as a thousands separator
european_value = '123.456,78';
#define decimal_dot
american_value = '123,456.78';
Since these values are guessed, there is no guarantee that they are interpreted the way you intended too, so use with care. However, if a value is clearly ambiguous, that is, if it can be interpreted in multiple ways, an error will be reported.
Math Parser follows to some extend the chrono library concepts.
- A date is a point in time. It has no length or duration. It has the members: day, month, year.
- A duration is a length of time. You can add and multiply them. It has the members: days, months, years. Note the plural form.
date - date ==> duration
date + duration ==> date
duration * x ==> duration // or any other arithmetic
Date values can be written as formatted values. Math Parser will try to parse any date format, as long as it's not ambiguous.
a='01/jan/2022';
a='2022/22/11';
a='2022/11/22';
meh='11/11/11'; //this works.
duh='1/1/2022'; //still works...
really='11/2022/11'; //yep, just fine...
a='2022/12/11'; //ambiguous
a='2/1/2022'; //ambiguous
Or, dates can be created by assigning a comma-separated list of values:
#define dmy; // or ymd or mdy
day = 23;
month = 12;
a_day = date(day, month/2, now().year); // june 23, of this year
The parts of a date can be referenced, but not assigned to:
thisMonth = now().month;
myDate.year=2022; //error: can't assign to a date
Thus, allowing for calculated values.
Note that you must #define
a strict date format, since changes in calculations could lead to the values suddenly being interpreted in a different order than what you intended.
[TODO]: allow access to date parts and date calculations, enforce a strict date format. Implement time.
A typical duration would be my age:
birthday=1968, 7, 30;
age=now()-birthday;
Some settings define how the parser behaves.
These can be changed with #define
and #undef
#define dmy // date formats can be dmy, mdy or ymd
trig //activate trigonometry functions,
date_units //day, month, year
short_date_units // d, M, y
arythm //abs(), round(),...
date //date(), now()
all // all functions
electric //numeric notations for resistors and capacitors
strict //trig functions will require params to have the units deg or rad where applicable.
decimal_dot // set decimal charater and thousands separaterd in a formatted string: american_value = '123.456,67';
dec_dot //short form
dot //shorter
decimal_comma //same as above, other locale: european_value = '123.456,78';
dec_comma
comma
decimal_auto //automatically determine the decimal char. Only works if both a `.` and `,` are present.
dec_auto
//no `auto` as this is a bit too general of a word.
A scope is defined by a code block: { ... }
. Within a scope, the external 'world' is unknown and vice-versa. A code block is thus a completely stand-alone program.
a=1;
{
b=a; //error: 'a' is unknown as it is external to the block.
c =3;
}
d=c; //error: 'c' is unknown outside of the block.
Code blocks allow to have a little program that 'does it's own thing' within a larger file. A typical use is to temporarily change some settings:
#define decimal_dot
theDot = '1,234.56';
{
#define decimal_comma
theDot = '1.234,56';
}
//a dot is still the decimal char in the outer scope:
theDot = '1,234.56';
Currently there's only one special character for which there's a shortcut::
Δ = 123; // Δ has shortcut Ctrl-Shift-e
It is a homebrew recursive descent parser with 2 look-aheads. Advantages of this parser type are that it's intuitive to read and mimicks the grammar definition (EBNF).
Those who are into parser development may want to take a look at the .ebnf
file (which stands for Erik's BNF :))
The previous verson of this project is written in C++ and can also be found on my github.
The parser is written in rust and compiled to wasm.
In addition rollup
is used to bundle the CodeMirror 6 online editor for the front end.
Note that a separate parser is written for CodeMirror syntax highlighting.
The (json) output of the rust parser is fed to CodeMirror's linter for error annotation.
There's a online version available at Google Cloud Platform.
This is a personal project, for fun.
Don't let your rocket launches depend on it.