Compare commits

...

10 Commits

Author SHA1 Message Date
d83106358f Math implementation 2023-12-26 13:36:04 +06:00
43d38645a5 fixed bugs 2023-12-26 00:28:46 +06:00
7dab069aa6 chmod 2023-12-25 21:37:57 +06:00
02a552767b renamed functions, added ctg and actg 2023-12-25 21:33:12 +06:00
e30cfc6cf4 scientific notation lexing 2023-12-25 21:11:13 +06:00
2a4f92fe35 function call literals parsing 2023-12-23 14:06:31 +06:00
8fe03105b7 fixed space skipping and decimal literals in Lexer 2023-12-23 13:54:35 +06:00
Timerix22
5907457907
Created README.md 2023-12-22 23:59:52 +06:00
726f4f2425 really implemented negative numbers 2023-12-22 23:10:48 +06:00
713784e783 build scripts 2023-12-22 22:43:08 +06:00
12 changed files with 294 additions and 64 deletions

View File

@ -1,10 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RollForward>LatestMajor</RollForward> <LangVersion>latest</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup> <Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="out\MainClass.cs" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>FusionCalculator</AssemblyName>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

44
README.md Normal file
View File

@ -0,0 +1,44 @@
# FusionCalculator
The calculator written in [Fusion language](https://github.com/fusionlanguage/fut). Can be translated to C and C#. Can be compiled as executable or library.
### Building
Requirements: fut, bash, dotnet8 (for c#), gcc (for C)
Just generate C# source files:
```shell
./build_cs.sh --translate-only
```
Build C# executable:
```shell
./build_cs.sh
```
Build C executable:
```shell
./build_c.sh
```
Build C executable with debug symbols:
```shell
./build_c.sh --debug
```
## Executable usage
Just call exe file in bin/ with math expression arguments:
```shell
bin/FusionCalculator.exe 1+2
3
bin/FusionCalculator.exe '11/(99-88)'
1
bin/FusionCalculator.exe '-1+6*(-2)'
-13
```
## Library usage
The public interface is very simple:
```cs
namespace FusionCalculator {
public static class Calculator {
public static double Calculate(string);
}
}
```
Just call Calculate and get the result!

24
build_c.sh Normal file → Executable file
View File

@ -6,12 +6,30 @@ BIN_FILE="bin/FusionCalculator.exe"
SRC_FILES="$(find src/ -name '*.fu')" SRC_FILES="$(find src/ -name '*.fu')"
WARNINGS="-Wall -Wno-unused-value -Wno-unused-function -Wno-unused-variable -Wno-discarded-qualifiers" WARNINGS="-Wall -Wno-unused-value -Wno-unused-function -Wno-unused-variable -Wno-discarded-qualifiers"
INCLUDES="$(pkg-config --cflags glib-2.0)" INCLUDES="$(pkg-config --cflags glib-2.0)"
COMPILER_ARGS="-O0 -g"
if [[ $1 == '--debug' ]]; then
COMPILER_ARGS="-O0 -g"
else
COMPILER_ARGS="-O2"
fi
LINKER_ARGS="$(pkg-config --libs glib-2.0) -lm" LINKER_ARGS="$(pkg-config --libs glib-2.0) -lm"
rm -rf out bin rm -rf out bin
mkdir out bin mkdir out bin
fut -l c -D C -o "$OUT_FILE" $SRC_FILES echo "------------[fut]------------"
gcc $WARNINGS $COMPILER_ARGS "$OUT_FILE" -o "$BIN_FILE" $INCLUDES $LINKER_ARGS args="-l c -D C -o "$OUT_FILE" $SRC_FILES"
if [[ $1 == '--implement-math-functions' || $2 == '--implement-math-functions' ]]; then
args="$args -D IMPLEMENT_MATH_FUNCTIONS"
fi
echo fut $args
fut $args
if [[ $1 != '--translate-only' && $2 != '--translate-only' ]]; then
echo "------------[gcc]------------"
args="$WARNINGS $COMPILER_ARGS "$OUT_FILE" -o "$BIN_FILE" $INCLUDES $LINKER_ARGS"
echo gcc $args
gcc $args
fi

15
build_cs.sh Normal file → Executable file
View File

@ -12,10 +12,19 @@ done
for src_file in $SRC_FILES; do for src_file in $SRC_FILES; do
echo "---------[$src_file]---------" echo "---------[$src_file]---------"
out_file="out/$(basename $src_file .fu).cs" out_file="out/$(basename $src_file .fu).cs"
args="-l cs -D CS -n FusionCalculator $INCLUDES -o $out_file $src_file" args="-l cs -D CS -n FusionCalculator $INCLUDES -o $out_file $src_file"
echo "fu $args" if [[ $1 == '--implement-math-functions' || $2 == '--implement-math-functions' ]]; then
args="$args -D IMPLEMENT_MATH_FUNCTIONS"
fi
echo fut $args
fut $args fut $args
done done
echo "---------[FusionCalculator.csproj]---------" if [[ $1 != '--translate-only' && $2 != '--translate-only' ]]; then
dotnet build FusionCalculator.csproj -o bin echo "---------[FusionCalculator.csproj]---------"
args="build -c Release FusionCalculator.exe.csproj -o bin"
echo dotnet $args
dotnet $args
fi

View File

@ -11,13 +11,43 @@ abstract class FunctionCallExpression : IExpression {
} }
class FunctionCallExpressionSin : FunctionCallExpression { class FunctionCallExpressionSin : FunctionCallExpression {
internal override double FunctionImplementation(double x) => Math.Sin(x); internal override double FunctionImplementation(double x) {
#if IMPLEMENT_MATH_FUNCTIONS
return MyMath.Sin(x);
#else
return Math.Sin(x);
#endif
}
} }
class FunctionCallExpressionCos : FunctionCallExpression { class FunctionCallExpressionCos : FunctionCallExpression {
internal override double FunctionImplementation(double x) => Math.Cos(x); internal override double FunctionImplementation(double x) {
#if IMPLEMENT_MATH_FUNCTIONS
return MyMath.Cos(x);
#else
return Math.Cos(x);
#endif
}
} }
class FunctionCallExpressionTan : FunctionCallExpression {
internal override double FunctionImplementation(double x) => Math.Tan(x); class FunctionCallExpressionTg : FunctionCallExpression {
internal override double FunctionImplementation(double x){
#if IMPLEMENT_MATH_FUNCTIONS
return MyMath.Tg(x);
#else
return Math.Tan(x);
#endif
}
}
class FunctionCallExpressionCtg : FunctionCallExpression {
internal override double FunctionImplementation(double x) {
#if IMPLEMENT_MATH_FUNCTIONS
return MyMath.Ctg(x);
#else
return 1 / Math.Tan(x);
#endif
}
} }
class FunctionCallExpressionAsin : FunctionCallExpression { class FunctionCallExpressionAsin : FunctionCallExpression {
@ -26,10 +56,13 @@ class FunctionCallExpressionAsin : FunctionCallExpression {
class FunctionCallExpressionAcos : FunctionCallExpression { class FunctionCallExpressionAcos : FunctionCallExpression {
internal override double FunctionImplementation(double x) => Math.Acos(x); internal override double FunctionImplementation(double x) => Math.Acos(x);
} }
class FunctionCallExpressionAtan : FunctionCallExpression { class FunctionCallExpressionAtg : FunctionCallExpression {
internal override double FunctionImplementation(double x) => Math.Atan(x); internal override double FunctionImplementation(double x) => Math.Atan(x);
} }
class FunctionCallExpressionActg : FunctionCallExpression {
internal override double FunctionImplementation(double x) => Math.Atan(1 / x);
}
class FunctionCallExpressionLog : FunctionCallExpression{ class FunctionCallExpressionLn : FunctionCallExpression{
internal override double FunctionImplementation(double x) => Math.Log(x); internal override double FunctionImplementation(double x) => Math.Log(x);
} }

50
src/Expressions/MyMath.fu Normal file
View File

@ -0,0 +1,50 @@
//
// My implementation of math functions using Taylor (Maclaurin) series
// https://en.wikipedia.org/wiki/Taylor_series#List_of_Maclaurin_series_of_some_common_functions
//
public class MyMath {
/// TODO: fix fail on tg(pi/2), tg(pi*), ctg(pi*2)
static double ClampRadians(double x){
int quotient = 0;
double pi2 = 2*Math.PI;
native {
quotient = (int)(x / pi2);
}
x -= pi2 * quotient;
return x;
}
public static double Sin(double x){
x = ClampRadians(x);
int iters = 16;
double pow = x;
double fact = 1;
double result = x;
for(int i = 3; i <= (2*iters+1); i+=2){
pow *= x*x; // x power +2
fact *= i * (i-1); // making i! from (i-2)!
fact *= -1; // change sign every iteration
result += pow/fact;
}
return result;
}
public static double Cos(double x){
x = ClampRadians(x);
int iters = 16;
double pow = 1;
double fact = 1;
double result = 1;
for(int i = 2; i <= (2*iters); i+=2){
pow *= x*x; // x power +2
fact *= i * (i-1); // making i! from (i-2)!
fact *= -1; // change sign every iteration
result += pow/fact;
}
return result;
}
public static double Tg(double x) => Sin(x)/Cos(x);
public static double Ctg(double x) => Cos(x)/Sin(x);
}

View File

@ -29,17 +29,26 @@ class OperatorExpressionDiv : OperatorExpression {
} }
class OperatorExpressionMod : OperatorExpression { class OperatorExpressionMod : OperatorExpression {
// returns if b>0 then returns a%b else returns a
internal override double OperatorImplementation(double a, double b) { internal override double OperatorImplementation(double a, double b) {
if(b <= 0) if(a == 0)
return b;
if(b == 0)
return a; return a;
if(a > 0){ if(a > 0){
while(a >= b) if(b > 0)
a -= b; while(a-b >= 0)
a -= b;
else
while(a+b >= 0)
a += b;
} }
else { else {
while(a <= b) if(b > 0)
a += b; while(a+b <= 0)
a += b;
else
while(a-b <= 0)
a -= b;
} }
return a; return a;
} }

View File

@ -37,7 +37,7 @@ class Lexer {
internal List<Token()> Lex!(string exprStr){ internal List<Token()> Lex!(string exprStr){
ExprStr = exprStr; ExprStr = exprStr;
while (i < ExprStr.Length) { for(i=0; i < ExprStr.Length; i++) {
switch (ExprStr[i]) { switch (ExprStr[i]) {
// end token, add new predifined token and move next // end token, add new predifined token and move next
case '(': AddStaticToken(TokBracketOpen); break; case '(': AddStaticToken(TokBracketOpen); break;
@ -47,21 +47,33 @@ class Lexer {
case '%': AddStaticToken(TokMod); break; case '%': AddStaticToken(TokMod); break;
case '/': AddStaticToken(TokDiv); break; case '/': AddStaticToken(TokDiv); break;
case '+': AddStaticToken(TokAdd); break; case '+': AddStaticToken(TokAdd); break;
case '-': AddStaticToken(TokSub); break; case '-':
// if '-' is not the first char and previous char isn't '(' or 'e' or 'E'
if(i != 0 && ExprStr[i-1] != '('
&& ExprStr[i-1] != 'e' && ExprStr[i-1] != 'E')
AddStaticToken(TokSub);
// else '-' is a part of numeric expression
break;
case 'e':
case 'E':
// if token starts with 'E' it is a literal
if(i == tokBegin)
tokType = Token.Type_Literal;
// else 'E' is a part of literal or number (sientific notation)
break;
// try end token and skip current char // try end token and skip current char
case ' ': case '\t': case '\n': case '\r': case ' ': case '\t': case '\n': case '\r':
TryEndToken(); TryEndToken();
i++; tokBegin++;
break; break;
// move next // move next
case '0': case '1': case '2': case '3': case '4': case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': case '5': case '6': case '7': case '8': case '9':
i++; case '.':
break; break;
// set type from Numeric to Literal // set type from Numeric to Literal
default: default:
tokType = Token.Type_Literal; tokType = Token.Type_Literal;
i++;
break; break;
} }
} }
@ -86,6 +98,6 @@ class Lexer {
TokenStorage.Add(); TokenStorage.Add();
TokenStorage[TokenStorage.Count-1] = tok; TokenStorage[TokenStorage.Count-1] = tok;
tokType = Token.Type_Number; tokType = Token.Type_Number;
tokBegin = ++i; tokBegin = i+1;
} }
} }

View File

@ -2,14 +2,15 @@ public static class MainClass {
public static void Main(string[] args){ public static void Main(string[] args){
#if CS #if CS
native { native {
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture; System.Globalization.CultureInfo.DefaultThreadCurrentCulture =
System.Globalization.CultureInfo.InvariantCulture;
} }
#endif #endif
string() joined = ""; string() joined = "";
foreach(string arg in args){ foreach(string arg in args){
joined += arg; joined += arg + " ";
} }
double rezult = Calculator.Calculate(joined); double result = Calculator.Calculate(joined);
Console.WriteLine(rezult); Console.WriteLine(result);
} }
} }

View File

@ -2,11 +2,9 @@ class Parser {
List<Token()> TokenStorage; List<Token()> TokenStorage;
IExpression# RootExpression; IExpression# RootExpression;
TokenLinkedList() TokensInRPN; TokenLinkedList() TokensInRPN;
Token() TokZero;
internal Parser(){ internal Parser(){
RootExpression = new NumericExpression(); // NaN RootExpression = new NumericExpression(); // NaN
TokZero = Token.Create("0", 0, 1, Token.Type_Number);
} }
internal IExpression# Parse!(List<Token()> tokens){ internal IExpression# Parse!(List<Token()> tokens){
@ -26,8 +24,6 @@ class Parser {
// Implementation of https://en.wikipedia.org/wiki/Shunting_yard_algorithm // Implementation of https://en.wikipedia.org/wiki/Shunting_yard_algorithm
void SortTokensInRPN!(){ void SortTokensInRPN!(){
Stack<Token>() RPNStack; Stack<Token>() RPNStack;
// is needed for negative numbers recognition
int prevTokType = Token.Type_BracketOpen;
for(int i = 0; i < TokenStorage.Count; i++){ for(int i = 0; i < TokenStorage.Count; i++){
Token tok = TokenStorage[i]; Token tok = TokenStorage[i];
@ -43,8 +39,7 @@ class Parser {
case Token.Type_OperatorMod: case Token.Type_OperatorMod:
case Token.Type_OperatorAdd: case Token.Type_OperatorAdd:
case Token.Type_OperatorSub: case Token.Type_OperatorSub:
if(type == Token.Type_OperatorSub && prevTokType == Token.Type_BracketOpen) case Token.Type_Literal:
TokensInRPN.AddToEnd(TokZero);
while(RPNStack.Count != 0 && RPNStack.Peek().GetTokType() >= type){ while(RPNStack.Count != 0 && RPNStack.Peek().GetTokType() >= type){
Token op2 = RPNStack.Pop(); Token op2 = RPNStack.Pop();
TokensInRPN.AddToEnd(op2); TokensInRPN.AddToEnd(op2);
@ -62,15 +57,10 @@ class Parser {
TokensInRPN.AddToEnd(op2); TokensInRPN.AddToEnd(op2);
} }
break; break;
case Token.Type_Literal:
ThrowError($"token '{tok.GetStr()}' isn't implemented");
break;
default: default:
ThrowError($"unexpected token type '{type}'"); ThrowError($"unexpected token type '{type}'");
break; break;
} }
prevTokType = type;
} }
// add remaining operators // add remaining operators
@ -88,30 +78,31 @@ class Parser {
while(tokenNode != null){ while(tokenNode != null){
Token tok = tokenNode.GetValue(); Token tok = tokenNode.GetValue();
int type = tok.GetTokType(); int type = tok.GetTokType();
string() str = tok.GetStr();
switch(type){ switch(type){
case Token.Type_Number: case Token.Type_Number:
NumericExpression# num = new NumericExpression(); NumericExpression# num = new NumericExpression();
num.Init(StringToDouble(tok.GetStr())); num.Init(StringToDouble(str));
expressionStack.Push(num); expressionStack.Push(num);
break; break;
case Token.Type_OperatorPow: case Token.Type_OperatorPow:
PushOperatorExpression(expressionStack, tok, new OperatorExpressionPow()); PushOperatorExpression(expressionStack, str, new OperatorExpressionPow());
break; break;
case Token.Type_OperatorMul: case Token.Type_OperatorMul:
PushOperatorExpression(expressionStack, tok, new OperatorExpressionMul()); PushOperatorExpression(expressionStack, str, new OperatorExpressionMul());
break; break;
case Token.Type_OperatorDiv: case Token.Type_OperatorDiv:
PushOperatorExpression(expressionStack, tok, new OperatorExpressionDiv()); PushOperatorExpression(expressionStack, str, new OperatorExpressionDiv());
break; break;
case Token.Type_OperatorMod: case Token.Type_OperatorMod:
PushOperatorExpression(expressionStack, tok, new OperatorExpressionMod()); PushOperatorExpression(expressionStack, str, new OperatorExpressionMod());
break; break;
case Token.Type_OperatorAdd: case Token.Type_OperatorAdd:
PushOperatorExpression(expressionStack, tok, new OperatorExpressionAdd()); PushOperatorExpression(expressionStack, str, new OperatorExpressionAdd());
break; break;
case Token.Type_OperatorSub: case Token.Type_OperatorSub:
PushOperatorExpression(expressionStack, tok, new OperatorExpressionSub()); PushOperatorExpression(expressionStack, str, new OperatorExpressionSub());
break; break;
case Token.Type_BracketClose: case Token.Type_BracketClose:
ThrowError("unexpected '('"); ThrowError("unexpected '('");
@ -120,19 +111,56 @@ class Parser {
ThrowError("unexpected ')'"); ThrowError("unexpected ')'");
break; break;
case Token.Type_Literal: case Token.Type_Literal:
ThrowError($"token '{tok.GetStr()}' isn't implemented"); switch(str){
case "sin":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionSin());
break;
case "cos":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionCos());
break;
case "tan":
case "tg":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionTg());
break;
case "ctg":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionCtg());
break;
case "asin":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionAsin());
break;
case "acos":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionAcos());
break;
case "atan":
case "atg":
case "arctg":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionAtg());
break;
case "actg":
case "arcctg":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionActg());
break;
case "ln":
PushFunctionExpression(expressionStack, str, new FunctionCallExpressionLn());
break;
default: {
ThrowError($"invalid literal '{str}'");
}
break;
}
break; break;
default: default: {
ThrowError($"unexpected token type '{type}'"); ThrowError($"unexpected token type '{type}'");
break; }
break;
} }
tokenNode = tokenNode.GetNext(); tokenNode = tokenNode.GetNext();
} }
if(expressionStack.Count == 1) if(expressionStack.Count != 1)
RootExpression = expressionStack.Pop(); ThrowError("");
else RootExpression = new NumericExpression(); // NaN RootExpression = expressionStack.Pop();
} }
// returns the number or NaN // returns the number or NaN
@ -154,9 +182,9 @@ class Parser {
return d; return d;
} }
void PushOperatorExpression(Stack<IExpression#>! expressionStack, Token tok, OperatorExpression# opExpr) { void PushOperatorExpression(Stack<IExpression#>! expressionStack, string tokStr, OperatorExpression# opExpr) {
if(expressionStack.Count < 2){ if(expressionStack.Count < 2){
ThrowError($"unexpected operator '{tok.GetStr()}'"); ThrowError($"unexpected operator '{tokStr}'");
return; return;
} }
IExpression b = expressionStack.Pop(); IExpression b = expressionStack.Pop();
@ -164,6 +192,15 @@ class Parser {
opExpr.Init(a, b); opExpr.Init(a, b);
expressionStack.Push(opExpr); expressionStack.Push(opExpr);
} }
void PushFunctionExpression(Stack<IExpression#>! expressionStack, string tokStr, FunctionCallExpression# fExpr) {
if(expressionStack.Count < 1){
ThrowError($"unexpected function call '{tokStr}'");
return;
}
IExpression x = expressionStack.Pop();
fExpr.Init(x);
expressionStack.Push(fExpr);
}
static void ThrowError(string errmsg){ static void ThrowError(string errmsg){
#if C #if C

View File

@ -6,16 +6,16 @@ class Token {
// The Type is also the priority of the token in calculation (see Parser). // The Type is also the priority of the token in calculation (see Parser).
int Type; int Type;
public const int Type_Literal=11;
public const int Type_OperatorPow=10; public const int Type_OperatorPow=10;
public const int Type_OperatorMul=9; public const int Type_OperatorMul=9;
public const int Type_OperatorMod=8; public const int Type_OperatorMod=8;
public const int Type_OperatorDiv=7; public const int Type_OperatorDiv=7;
public const int Type_OperatorAdd=6; public const int Type_OperatorAdd=6;
public const int Type_OperatorSub=5; public const int Type_OperatorSub=5;
public const int Type_BracketOpen=4; public const int Type_BracketOpen=3;
public const int Type_BracketClose=3; public const int Type_BracketClose=2;
public const int Type_Number=2; public const int Type_Number=1;
public const int Type_Literal=1;
internal static Token() Create(string str, int startIndex, int length, int type){ internal static Token() Create(string str, int startIndex, int length, int type){
Token() tok = { Token() tok = {