Login
7 branches 0 tags
Ben (X13/Arch) Fixed efficient funcalls, and Minor cleanups e5d7375 3 years ago 923 Commits
nujel / lib / operations / arithmetic.c
/* Nujel - Copyright (C) 2020-2022 - Benjamin Vincent Schulenburg
 * This project uses the MIT license, a copy should be included under /LICENSE */
#ifndef NUJEL_AMALGAMATION
#include "../nujel-private.h"
#endif

#include <math.h>
#include <stdlib.h>

static lVal *exceptionThrow(lClosure *c, lVal *v, const char *func){
	(void)func;
	lExceptionThrowValClo("type-error","Can't calculate with non numeric types, please explicitly convert into a numeric form using [int α],[float β],[vec γ].",v, c);
	return NULL;
}

static lVal *exceptionThrowFloat(lClosure *c, lVal *v, const char *func){
	(void)func;
	lExceptionThrowValClo("type-error","This function can only be used with floats, you can use [float α] to explicitly convert into a floating point value",v, c);
	return NULL;
}

lVal *lAdd(lClosure *c, lVal *a, lVal *b){
	if(unlikely(a == NULL)){return lValInt(0);}
	if(unlikely(b == NULL)){return a;}
	lType t = lTypecast(a->type, b->type);
	switch(t){
		default:      return exceptionThrow(c, a,"addition");
		case ltInt:   return lValInt(requireInt(c,a) + requireInt(c,b));
		case ltFloat: return lValFloat(c, requireFloat(c,a) + requireFloat(c,b));
	}
}

lVal *lSub(lClosure *c, lVal *a, lVal *b){
	if(unlikely(a == NULL)){ throwArityError(c, a, 2); }
	if(unlikely(b == NULL)){
		switch(a->type){
		default:      return exceptionThrow(c, a,"subtraction");
		case ltInt:   return lValInt(-a->vInt);
		case ltFloat: return lValFloat(c, -a->vFloat);
		}
	}
	lType t = lTypecast(a->type, b->type);
	switch(t){
		default:      return exceptionThrow(c, a,"subtraction");
		case ltInt:   return lValInt(requireInt(c,a) - requireInt(c,b));
		case ltFloat: return lValFloat(c, requireFloat(c,a) - requireFloat(c,b));
	}
}

lVal *lMul(lClosure *c, lVal *a, lVal *b){
	if(unlikely(a == NULL)){return lValInt(1);}
	if(unlikely(b == NULL)){
		throwArityError(c, b, 2);
	}
	lType t = lTypecast(a->type, b->type);
	switch(t){
		default:      return exceptionThrow(c, a,"multiplication");
		case ltInt:   return lValInt(requireInt(c,a) * requireInt(c,b));
		case ltFloat: return lValFloat(c, requireFloat(c,a) * requireFloat(c,b));
	}
}

lVal *lDiv(lClosure *c, lVal *a, lVal *b){
	if(unlikely((a == NULL) || (b == NULL))){throwArityError(c, b, 2);}
	lType t = lTypecast(a->type, b->type);
	switch(t){
		default: return exceptionThrow(c, a,"division");
		case ltInt:
		case ltFloat: return lValFloat(c,requireFloat(c,a) / requireFloat(c,b));
	}
}

lVal *lRem(lClosure *c, lVal *a, lVal *b){
	if(unlikely(a == NULL)){return b;}
	if(unlikely(b == NULL)){return a;}
	lType t = lTypecast(a->type, b->type);
	switch(t){
		default:      return exceptionThrow(c, a,"module");
		case ltInt: {
			const i64 av = requireInt(c,a);
			const i64 bv = requireInt(c,b);
			if(bv == 0){lExceptionThrowValClo("division-by-zero","Module/Dividing by zero is probably not what you wanted", NULL, c);}
			return lValInt(av % bv);}
		case ltFloat: return lValFloat(c, fmod(requireFloat(c,a), requireFloat(c,b)));
	}
}

static lVal *lnfAdd(lClosure *c, lVal *v){
	return lAdd(c, lCar(v), lCadr(v));
}

static lVal *lnfSub(lClosure *c, lVal *v){
	return lSub(c, lCar(v), lCadr(v));
}

static lVal *lnfMul(lClosure *c, lVal *v){
	return lMul(c, lCar(v), lCadr(v));
}

static lVal *lnfDiv(lClosure *c, lVal *v){
	return lDiv(c, lCar(v), lCadr(v));
}

static lVal *lnfRem(lClosure *c, lVal *v){
	return lRem(c, lCar(v), lCadr(v));
}

static lVal *lnfPow(lClosure *c, lVal *v){
	lVal *a = lCar(v);
	lVal *b = lCadr(v);
	if(unlikely(b == NULL)){return a;}
	if(unlikely(a == NULL)){
		throwArityError(c, v, 2);
	}
	lType t = lTypecast(a->type, b->type);
	switch(t){
		default:      return exceptionThrowFloat(c, v,"power");
		case ltInt:   return lValInt(pow(requireInt(c,a),  requireInt(c,b)));
		case ltFloat: return lValFloat(c, pow(requireFloat(c,a), requireFloat(c,b)));
	}
}

static lVal *lnfIncAstI(lClosure *c, lVal *v){
	if(unlikely(v == NULL) || unlikely(v->vList.car == NULL)){
		return exceptionThrow(c, v, "inc/int");
	}
	const i64 a = v->vList.car->vInt;
	return lValInt(a + 1);
}

static lVal *lnfAddAstI(lClosure *c, lVal *v){
	(void)c;
	const i64 a = v->vList.car->vInt;
	const i64 b = v->vList.cdr->vList.car->vInt;
	return lValInt(a + b);
}

static lVal *lnfSubAstI(lClosure *c, lVal *v){
	(void)c;
	const i64 a = v->vList.car->vInt;
	const i64 b = v->vList.cdr->vList.car->vInt;
	return lValInt(a - b);
}

static lVal *lnfMulAstI(lClosure *c, lVal *v){
	(void)c;
	const i64 a = v->vList.car->vInt;
	const i64 b = v->vList.cdr->vList.car->vInt;
	return lValInt(a * b);
}

static lVal *lnfDivAstI(lClosure *c, lVal *v){
	(void)c;
	if(unlikely(((v == NULL) || (v->vList.car == NULL) || (v->vList.cdr == NULL) || (v->vList.cdr->vList.car == NULL)))){
		lExceptionThrowValClo("arity-error", "Expected 2 arguments", v, c);
	}
	const i64 a = v->vList.car->vInt;
	const i64 b = v->vList.cdr->vList.car->vInt;
	if(unlikely(b == 0)){
		lExceptionThrowValClo("divide-by-zero", "Can't divide by zero", v, c);
	}
	return lValInt(a / b);
}

static lVal *lnfModAstI(lClosure *c, lVal *v){
	(void)c;
	const i64 a = v->vList.car->vInt;
	const i64 b = v->vList.cdr->vList.car->vInt;
	return lValInt(a % b);
}

static lVal *lnfPowAstI(lClosure *c, lVal *v){
	(void)c;
	const i64 a = v->vList.car->vInt;
	const i64 b = v->vList.cdr->vList.car->vInt;
	return lValInt(pow(a,b));
}

static lVal *lnfLogAnd(lClosure *c, lVal *v){
	return lValInt(requireInt(c, lCar(v)) & requireInt(c, lCadr(v)));
}

static lVal *lnfLogIor(lClosure *c, lVal *v){
	return lValInt(requireInt(c, lCar(v)) | requireInt(c, lCadr(v)));
}

static lVal *lnfLogXor(lClosure *c, lVal *v){
        return lValInt(requireInt(c, lCar(v)) ^ requireInt(c, lCadr(v)));
}

static lVal *lnfLogNot(lClosure *c, lVal *v){
	return lValInt(~requireInt(c, lCar(v)));
}

static lVal *lnfPopCount(lClosure *c, lVal *v){
	const i64 iv = requireInt(c, lCar(v));
#ifdef _MSC_VER
	return lValInt(__popcnt64(iv));
#else
	return lValInt(__builtin_popcountll(iv));
#endif
}

static lVal *lnfAsh(lClosure *c, lVal *v){
	const u64 iv = requireInt(c, lCar(v));
	const i64 sv = requireInt(c, lCadr(v));
	return lValInt((sv > 0) ? (iv <<  sv) : (iv >> -sv));
}

lVal *lnfAbs(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrow(c, v,"absolute");
		case ltFloat: return lValFloat(c,fabs(t->vFloat));
		case ltInt:   return lValInt(llabs(t->vInt));
	}
}

lVal *lnfCbrt(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrow(c, v,"squareroot");
		case ltFloat: return lValFloat(c, cbrt(t->vFloat));
		case ltInt:   return lValFloat(c, cbrt(t->vInt));
	}
}

lVal *lnfSqrt(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrow(c, v,"squareroot");
		case ltFloat: return lValFloat(c, sqrt(t->vFloat));
		case ltInt:   return lValFloat(c, sqrt(t->vInt));
	}
}

lVal *lnfCeil(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrow(c, v,"ceil");
		case ltFloat: return lValFloat(c, ceil(t->vFloat));
	}
}

lVal *lnfFloor(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrow(c, v,"floor");
		case ltFloat: return lValFloat(c, floor(t->vFloat));
	}
}

lVal *lnfRound(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrow(c, v,"round");
		case ltFloat: return lValFloat(c, round(t->vFloat));
	}
}

lVal *lnfSin(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrowFloat(c, v,"sin");
		case ltFloat: return lValFloat(c, sin(t->vFloat));
	}
}

lVal *lnfCos(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrowFloat(c, v,"cos");
		case ltFloat: return lValFloat(c, cos(t->vFloat));
	}
}

lVal *lnfTan(lClosure *c, lVal *v){
	lVal *t = lCar(v);
	typeswitch(t){
		default:      return exceptionThrowFloat(c, v,"tan");
		case ltFloat: return lValFloat(c, tan(t->vFloat));
	}
}

lVal *lnfAtanTwo(lClosure *c, lVal *v){
	const double y = requireFloat(c,  lCar(v));
	const double x = requireFloat(c, lCadr(v));
	return lValFloat(c, atan2(y, x));
}

void lOperationsArithmetic(lClosure *c){
	lAddNativeFuncPureFold(c,"+",   "[a b]", "Addition",      lnfAdd);
	lAddNativeFuncPureFold(c,"-",   "[a b]", "Substraction",  lnfSub);
	lAddNativeFuncPureFold(c,"*",   "[a b]", "Multiplication",lnfMul);
	lAddNativeFuncPureFold(c,"/",   "[a b]", "Division",      lnfDiv);
	lAddNativeFuncPureFold(c,"rem", "[a b]", "Remainder",     lnfRem);
	lAddNativeFuncPureFold(c,"pow", "[a b]", "Return A raised to the power of B",lnfPow);

	lAddNativeFuncPureFold(c,"add/int", "[a b]", "Return a:int + b:int",  lnfAddAstI);
	lAddNativeFuncPureFold(c,"sub/int", "[a b]", "Return a:int - b:int",  lnfSubAstI);
	lAddNativeFuncPureFold(c,"mul/int", "[a b]", "Return a:int * b:int",  lnfMulAstI);
	lAddNativeFuncPureFold(c,"div/int", "[a b]", "Return a:int / b:int",  lnfDivAstI);
	lAddNativeFuncPureFold(c,"mod/int", "[a b]", "Return a:int % b:int",  lnfModAstI);
	lAddNativeFuncPureFold(c,"pow/int", "[a b]", "Return a:int ** b:int", lnfPowAstI);
	lAddNativeFuncPureFold(c,"inc/int", "[a]",   "Return a:int + 1",      lnfIncAstI);

	lAddNativeFuncPureFold(c,"bit-and",  "[a b]", "Bitwise and",          lnfLogAnd);
	lAddNativeFuncPureFold(c,"bit-or",   "[a b]", "Bitwise or",           lnfLogIor);
	lAddNativeFuncPureFold(c,"bit-xor",  "[a b]", "Bitwise exclusive or", lnfLogXor);
	lAddNativeFuncPureFold(c,"bit-not",  "[a]",   "Bitwise not",          lnfLogNot);

	lAddNativeFuncPure(c,"bit-shift-left", "[val amount]","Shift VALUE left AMOUNT bits",    lnfAsh);
	lAddNativeFuncPure(c,"popcount",       "[val]",       "Return amount of bits set in VAL",lnfPopCount);

	lAddNativeFuncPure(c,"abs",  "[a]", "Return the absolute value of a", lnfAbs);
	lAddNativeFuncPure(c,"sqrt", "[a]", "Return the square root of a",    lnfSqrt);
	lAddNativeFuncPure(c,"cbrt", "[a]", "Return the cube root of a",      lnfCbrt);
	lAddNativeFuncPure(c,"floor","[a]", "Round a down",                   lnfFloor);
	lAddNativeFuncPure(c,"ceil", "[a]", "Round a up",                     lnfCeil);
	lAddNativeFuncPure(c,"round","[a]", "Round a",                        lnfRound);
	lAddNativeFuncPure(c,"sin",  "[a]", "Sin A",                          lnfSin);
	lAddNativeFuncPure(c,"cos",  "[a]", "Cos A",                          lnfCos);
	lAddNativeFuncPure(c,"tan",  "[a]", "Tan A",                          lnfTan);
	lAddNativeFuncPure(c,"atan2","[y x]", "Arc tangent of y/x",           lnfAtanTwo);
}