shithub: rgbds

Download patch

ref: 895ec5564d85822289c250d115fc7fec085d4cd7
parent: 7bb6f71f0b51afd06446fe5138f5e90f996af7e4
author: Rangi <35663410+Rangi42@users.noreply.github.com>
date: Fri Jan 1 14:39:20 EST 2021

Update mathematical functions (#675)

Document the existing `ROUND`, `CEIL`, and `FLOOR` functions
Also update the trig function docs for searchability

Implement `POW` and `LOG`
Addresses part of #675

Implement ** for integer exponents
** has higher precedence than -, like Python, so -3**4 == -(3**4) == 81

--- a/include/asm/mymath.h
+++ b/include/asm/mymath.h
@@ -22,6 +22,8 @@
 int32_t math_ATan2(int32_t i, int32_t j);
 int32_t math_Mul(int32_t i, int32_t j);
 int32_t math_Div(int32_t i, int32_t j);
+int32_t math_Pow(int32_t i, int32_t j);
+int32_t math_Log(int32_t i, int32_t j);
 int32_t math_Round(int32_t i);
 int32_t math_Ceil(int32_t i);
 int32_t math_Floor(int32_t i);
--- a/include/linkdefs.h
+++ b/include/linkdefs.h
@@ -14,7 +14,7 @@
 
 #define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
 #define RGBDS_OBJECT_VERSION_NUMBER 9U
-#define RGBDS_OBJECT_REV 6U
+#define RGBDS_OBJECT_REV 7U
 
 enum AssertionType {
 	ASSERT_WARN,
@@ -29,6 +29,7 @@
 	RPN_DIV		= 0x03,
 	RPN_MOD		= 0x04,
 	RPN_UNSUB	= 0x05,
+	RPN_EXP		= 0x06,
 
 	RPN_OR		= 0x10,
 	RPN_AND		= 0x11,
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -182,6 +182,8 @@
 	{"FLOOR", T_OP_FLOOR},
 	{"DIV", T_OP_FDIV},
 	{"MUL", T_OP_FMUL},
+	{"POW", T_OP_POW},
+	{"LOG", T_OP_LOG},
 	{"SIN", T_OP_SIN},
 	{"COS", T_OP_COS},
 	{"TAN", T_OP_TAN},
@@ -488,7 +490,7 @@
 	uint16_t children[0x60 - ' '];
 	struct KeywordMapping const *keyword;
 /* Since the keyword structure is invariant, the min number of nodes is known at compile time */
-} keywordDict[350] = {0}; /* Make sure to keep this correct when adding keywords! */
+} keywordDict[352] = {0}; /* Make sure to keep this correct when adding keywords! */
 
 /* Convert a char into its index into the dict */
 static inline uint8_t dictIndex(char c)
@@ -1606,13 +1608,20 @@
 		 lexer_GetLineNo(), lexer_GetColNo());
 	for (;;) {
 		int c = nextChar();
+		char secondChar;
 
 		switch (c) {
 		/* Ignore whitespace and comments */
 
 		case '*':
-			if (!lexerState->atLineStart)
+			if (!lexerState->atLineStart) { /* Either MUL or EXP */
+				secondChar = peek(0);
+				if (secondChar == '*') {
+					shiftChars(1);
+					return T_OP_EXP;
+				}
 				return T_OP_MUL;
+			}
 			warning(WARNING_OBSOLETE,
 				"'*' is deprecated for comments, please use ';' instead\n");
 			/* fallthrough */
@@ -1651,7 +1660,6 @@
 			return T_COMMA;
 
 		/* Handle ambiguous 1- or 2-char tokens */
-		char secondChar;
 		case '/': /* Either division or a block comment */
 			secondChar = peek(0);
 			if (secondChar == '*') {
--- a/src/asm/math.c
+++ b/src/asm/math.c
@@ -126,6 +126,22 @@
 }
 
 /*
+ * Power
+ */
+int32_t math_Pow(int32_t i, int32_t j)
+{
+	return double2fx(pow(fx2double(i), fx2double(j)));
+}
+
+/*
+ * Logarithm
+ */
+int32_t math_Log(int32_t i, int32_t j)
+{
+	return double2fx(log(fx2double(i)) / log(fx2double(j)));
+}
+
+/*
  * Round
  */
 int32_t math_Round(int32_t i)
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -353,6 +353,11 @@
 %left	T_OP_SHL T_OP_SHR
 %left	T_OP_MUL T_OP_DIV T_OP_MOD
 %left	T_OP_NOT
+
+%left	NEG /* negation -- unary minus */
+
+%left	T_OP_EXP
+
 %left	T_OP_DEF
 %left	T_OP_BANK T_OP_ALIGN
 %left	T_OP_SIN
@@ -364,6 +369,8 @@
 %left	T_OP_ATAN2
 %left	T_OP_FDIV
 %left	T_OP_FMUL
+%left	T_OP_POW
+%left	T_OP_LOG
 %left	T_OP_ROUND
 %left	T_OP_CEIL
 %left	T_OP_FLOOR
@@ -381,8 +388,6 @@
 %left	T_OP_STRLWR
 %left	T_OP_STRFMT
 
-%left	NEG /* negation -- unary minus */
-
 %token	<tzSym> T_LABEL
 %token	<tzSym> T_ID
 %token	<tzSym> T_LOCAL_ID
@@ -1131,6 +1136,9 @@
 		| relocexpr T_OP_MOD relocexpr {
 			rpn_BinaryOp(RPN_MOD, &$$, &$1, &$3);
 		}
+		| relocexpr T_OP_EXP relocexpr {
+			rpn_BinaryOp(RPN_EXP, &$$, &$1, &$3);
+		}
 		| T_OP_ADD relocexpr %prec NEG	{ $$ = $2; }
 		| T_OP_SUB relocexpr %prec NEG	{ rpn_UNNEG(&$$, &$2); }
 		| T_OP_NOT relocexpr %prec NEG	{ rpn_UNNOT(&$$, &$2); }
@@ -1165,6 +1173,12 @@
 		}
 		| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
 			rpn_Number(&$$, math_Mul($3, $5));
+		}
+		| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
+			rpn_Number(&$$, math_Pow($3, $5));
+		}
+		| T_OP_LOG T_LPAREN const T_COMMA const T_RPAREN {
+			rpn_Number(&$$, math_Log($3, $5));
 		}
 		| T_OP_SIN T_LPAREN const T_RPAREN {
 			rpn_Number(&$$, math_Sin($3));
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -131,6 +131,7 @@
 .It Sy Operator Ta Sy Meaning
 .It Li \&( \&) Ta Precedence override
 .It Li FUNC() Ta Built-in function call
+.It Li ** Ta Exponent
 .It Li ~ + - Ta Unary complement/plus/minus
 .It Li * / % Ta Multiply/divide/modulo
 .It Li << >> Ta Shift left/right
@@ -190,12 +191,17 @@
 .It Sy Name Ta Sy Operation
 .It Fn DIV x y Ta $x \[di] y$
 .It Fn MUL x y Ta $x \[mu] y$
-.It Fn SIN x Ta $sin ( x )$
-.It Fn COS x Ta $cos ( x )$
-.It Fn TAN x Ta $tan ( x )$
-.It Fn ASIN x Ta $asin ( x )$
-.It Fn ACOS x Ta $acos ( x )$
-.It Fn ATAN x Ta $atan ( x )$
+.It Fn POW x y Ta $x$ to the $y$ power
+.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
+.It Fn ROUND x Ta Round $x$ to the nearest integer
+.It Fn CEIL x Ta Round $x$ up to an integer
+.It Fn FLOOR x Ta Round $x$ down to an integer
+.It Fn SIN x Ta Sine of $x$
+.It Fn COS x Ta Cosine of $x$
+.It Fn TAN x Ta Tangent of $x$
+.It Fn ASIN x Ta Inverse sine of $x$
+.It Fn ACOS x Ta Inverse cosine of $x$
+.It Fn ATAN x Ta Inverse tangent of $x$
 .It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$
 .El
 .EQ
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -292,6 +292,20 @@
 	}
 }
 
+static int32_t exponent(int32_t base, int32_t power)
+{
+	int32_t result = 1;
+
+	while (power) {
+		if (power % 2)
+			result *= base;
+		power /= 2;
+		base *= base;
+	}
+
+	return result;
+}
+
 struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
 {
 	if (!rpn_isSymbol(expr))
@@ -414,6 +428,15 @@
 				expr->nVal = 0;
 			else
 				expr->nVal = src1->nVal % src2->nVal;
+			break;
+		case RPN_EXP:
+			if (src2->nVal < 0)
+				fatalerror("Exponentiation by negative power\n");
+
+			if (src1->nVal == INT32_MIN && src2->nVal == -1)
+				expr->nVal = 0;
+			else
+				expr->nVal = exponent(src1->nVal, src2->nVal);
 			break;
 
 		case RPN_UNSUB:
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -21,6 +21,20 @@
 
 #include "extern/err.h"
 
+static int32_t exponent(int32_t base, int32_t power)
+{
+	int32_t result = 1;
+
+	while (power) {
+		if (power % 2)
+			result *= base;
+		power /= 2;
+		base *= base;
+	}
+
+	return result;
+}
+
 static int32_t asl(int32_t value, int32_t shiftamt); // Forward decl for below
 static int32_t asr(int32_t value, int32_t shiftamt)
 {
@@ -236,6 +250,18 @@
 			break;
 		case RPN_UNSUB:
 			value = -popRPN();
+			break;
+		case RPN_EXP:
+			value = popRPN();
+			if (value < 0) {
+				if (!isError)
+					error(patch->src, patch->lineNo, "Exponent by negative");
+				isError = true;
+				popRPN();
+				value = 0;
+			} else {
+				value = exponent(popRPN(), value);
+			}
 			break;
 
 		case RPN_OR:
--- /dev/null
+++ b/test/asm/math.asm
@@ -1,0 +1,40 @@
+X equ 0
+
+test: MACRO
+; Test RGBASM
+v equs "X +"
+	static_assert \#
+	purge v
+; Test RGBLINK
+v equs "Y +"
+	assert \#
+	purge v
+ENDM
+
+	test (v 2)*(v 10)**(v 2)*(v 2) == (v 400)
+	test -(v 3)**(v 4) == v -81
+
+	assert DIV(5.0, 2.0) == 2.5
+	assert DIV(-5.0, 2.0) == -2.5
+	assert DIV(-5.0, 0.0) == $8000_0000
+
+	assert MUL(10.0, 0.5) == 5.0
+	assert MUL(10.0, 0.0) == 0.0
+
+	assert POW(10.0, 2.0) == 100.0
+	assert POW(100.0, 0.5) == 10.0
+
+	assert LOG(100.0, 10.0) == 2.0
+	assert LOG(256.0, 2.0) == 8.0
+
+	assert ROUND(1.5) == 2.0
+	assert ROUND(-1.5) == -2.0
+
+	assert CEIL(1.5) == 2.0
+	assert CEIL(-1.5) == -1.0
+
+	assert FLOOR(1.5) == 1.0
+	assert FLOOR(-1.5) == -2.0
+
+SECTION "Y", ROM0
+Y::