shithub: rgbds

Download patch

ref: 3806eb313951425fe557624f8add92689020022c
parent: 5cb6c4af4bdf4f9cbcab531fa85f9a07130f3dd9
author: dbrotz <43593771+dbrotz@users.noreply.github.com>
date: Sun Dec 2 08:49:12 EST 2018

Fix ambiguity in const parsing

--- a/Makefile
+++ b/Makefile
@@ -48,6 +48,7 @@
 rgbasm_obj := \
 	src/asm/asmy.o \
 	src/asm/charmap.o \
+	src/asm/constexpr.o \
 	src/asm/fstack.o \
 	src/asm/globlex.o \
 	src/asm/lexer.o \
@@ -61,7 +62,7 @@
 	src/version.o
 
 src/asm/asmy.h: src/asm/asmy.c
-src/asm/locallex.o src/asm/globlex.o src/asm/lexer.o: src/asm/asmy.h
+src/asm/locallex.o src/asm/globlex.o src/asm/lexer.o src/asm/constexpr.o: src/asm/asmy.h
 
 rgblink_obj := \
 	src/link/assign.o \
--- /dev/null
+++ b/include/asm/constexpr.h
@@ -1,0 +1,33 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef RGBDS_ASM_CONSTEXPR_H
+#define RGBDS_ASM_CONSTEXPR_H
+
+#include <stdint.h>
+
+struct ConstExpression {
+	union {
+		int32_t nVal;
+		struct sSymbol *pSym;
+	} u;
+	uint32_t isSym;
+};
+
+void constexpr_Symbol(struct ConstExpression *expr, char *tzSym);
+void constexpr_Number(struct ConstExpression *expr, int32_t i);
+void constexpr_UnaryOp(struct ConstExpression *expr,
+		       int32_t op,
+		       const struct ConstExpression *src);
+void constexpr_BinaryOp(struct ConstExpression *expr,
+			int32_t op,
+			const struct ConstExpression *src1,
+			const struct ConstExpression *src2);
+int32_t constexpr_GetConstantValue(struct ConstExpression *expr);
+
+#endif /* RGBDS_ASM_CONSTEXPR_H */
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -17,6 +17,7 @@
 
 #include "asm/asm.h"
 #include "asm/charmap.h"
+#include "asm/constexpr.h"
 #include "asm/fstack.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
@@ -438,10 +439,11 @@
 	char tzString[MAXSTRLEN + 1];
 	struct Expression sVal;
 	int32_t nConstValue;
+	struct ConstExpression sConstExpr;
 }
 
 %type	<sVal>		relocconst
-%type	<nConstValue>	const
+%type	<sConstExpr>	const
 %type	<nConstValue>	uconst
 %type	<nConstValue>	const_3bit
 %type	<sVal>		const_8bit
@@ -453,38 +455,38 @@
 %token	<nConstValue>	T_NUMBER
 %token	<tzString>	T_STRING
 
-%left	T_OP_LOGICNOT
-%left	T_OP_LOGICOR T_OP_LOGICAND T_OP_LOGICEQU
-%left	T_OP_LOGICGT T_OP_LOGICLT T_OP_LOGICGE T_OP_LOGICLE T_OP_LOGICNE
-%left	T_OP_ADD T_OP_SUB
-%left	T_OP_OR T_OP_XOR T_OP_AND
-%left	T_OP_SHL T_OP_SHR
-%left	T_OP_MUL T_OP_DIV T_OP_MOD
-%left	T_OP_NOT
-%left	T_OP_DEF
-%left	T_OP_BANK T_OP_ALIGN
-%left	T_OP_SIN
-%left	T_OP_COS
-%left	T_OP_TAN
-%left	T_OP_ASIN
-%left	T_OP_ACOS
-%left	T_OP_ATAN
-%left	T_OP_ATAN2
-%left	T_OP_FDIV
-%left	T_OP_FMUL
-%left	T_OP_ROUND
-%left	T_OP_CEIL
-%left	T_OP_FLOOR
+%left	<nConstValue>	T_OP_LOGICNOT
+%left	<nConstValue>	T_OP_LOGICOR T_OP_LOGICAND T_OP_LOGICEQU
+%left	<nConstValue>	T_OP_LOGICGT T_OP_LOGICLT T_OP_LOGICGE T_OP_LOGICLE T_OP_LOGICNE
+%left	<nConstValue>	T_OP_ADD T_OP_SUB
+%left	<nConstValue>	T_OP_OR T_OP_XOR T_OP_AND
+%left	<nConstValue>	T_OP_SHL T_OP_SHR
+%left	<nConstValue>	T_OP_MUL T_OP_DIV T_OP_MOD
+%left	<nConstValue>	T_OP_NOT
+%left	<nConstValue>	T_OP_DEF
+%left	<nConstValue>	T_OP_BANK T_OP_ALIGN
+%left	<nConstValue>	T_OP_SIN
+%left	<nConstValue>	T_OP_COS
+%left	<nConstValue>	T_OP_TAN
+%left	<nConstValue>	T_OP_ASIN
+%left	<nConstValue>	T_OP_ACOS
+%left	<nConstValue>	T_OP_ATAN
+%left	<nConstValue>	T_OP_ATAN2
+%left	<nConstValue>	T_OP_FDIV
+%left	<nConstValue>	T_OP_FMUL
+%left	<nConstValue>	T_OP_ROUND
+%left	<nConstValue>	T_OP_CEIL
+%left	<nConstValue>	T_OP_FLOOR
 
-%token	T_OP_HIGH T_OP_LOW
+%token	<nConstValue>	T_OP_HIGH T_OP_LOW
 
-%left	T_OP_STRCMP
-%left	T_OP_STRIN
-%left	T_OP_STRSUB
-%left	T_OP_STRLEN
-%left	T_OP_STRCAT
-%left	T_OP_STRUPR
-%left	T_OP_STRLWR
+%left	<nConstValue>	T_OP_STRCMP
+%left	<nConstValue>	T_OP_STRIN
+%left	<nConstValue>	T_OP_STRSUB
+%left	<nConstValue>	T_OP_STRLEN
+%left	<nConstValue>	T_OP_STRCAT
+%left	<nConstValue>	T_OP_STRUPR
+%left	<nConstValue>	T_OP_STRLWR
 
 %left	NEG /* negation -- unary minus */
 
@@ -883,13 +885,13 @@
 
 equ		: T_LABEL T_POP_EQU const
 		{
-			sym_AddEqu($1, $3);
+			sym_AddEqu($1, constexpr_GetConstantValue(&$3));
 		}
 ;
 
 set		: T_LABEL T_POP_SET const
 		{
-			sym_AddSet($1, $3);
+			sym_AddSet($1, constexpr_GetConstantValue(&$3));
 		}
 ;
 
@@ -918,7 +920,7 @@
 		}
 		| T_POP_CHARMAP string comma const
 		{
-			if (charmap_Add($2, $4 & 0xFF) == -1) {
+			if (charmap_Add($2, constexpr_GetConstantValue(&$4) & 0xFF) == -1) {
 				fprintf(stderr, "Error parsing charmap. Either you've added too many (%i), or the input character length is too long (%i)' : %s\n", MAXCHARMAPS, CHARMAPLENGTH, strerror(errno));
 				yyerror("Error parsing charmap.");
 			}
@@ -935,7 +937,7 @@
 printv		: T_POP_PRINTV const
 		{
 			if (nPass == 1)
-				printf("$%X", $2);
+				printf("$%X", constexpr_GetConstantValue(&$2));
 		}
 ;
 
@@ -942,7 +944,7 @@
 printi		: T_POP_PRINTI const
 		{
 			if (nPass == 1)
-				printf("%d", $2);
+				printf("%d", constexpr_GetConstantValue(&$2));
 		}
 ;
 
@@ -949,7 +951,7 @@
 printf		: T_POP_PRINTF const
 		{
 			if (nPass == 1)
-				math_Print($2);
+				math_Print(constexpr_GetConstantValue(&$2));
 		}
 ;
 
@@ -956,7 +958,7 @@
 if		: T_POP_IF const
 		{
 			nIFDepth++;
-			if (!$2) {
+			if (!constexpr_GetConstantValue(&$2)) {
 				/*
 				 * Continue parsing after ELSE, or at ELIF or
 				 * ENDC keyword.
@@ -988,7 +990,7 @@
 				 */
 				skipElif = true;
 
-				if (!$2) {
+				if (!constexpr_GetConstantValue(&$2)) {
 					/*
 					 * Continue parsing after ELSE, or at
 					 * ELIF or ENDC keyword.
@@ -1020,10 +1022,11 @@
 
 const_3bit	: const
 		{
-			if (($1 < 0) || ($1 > 7))
+			int32_t value = constexpr_GetConstantValue(&$1);
+			if ((value < 0) || (value > 7))
 				yyerror("Immediate value must be 3-bit");
 			else
-				$$ = $1 & 0x7;
+				$$ = value & 0x7;
 		}
 ;
 
@@ -1190,18 +1193,60 @@
 			rpn_Number(&$$, sym_isConstDefined($4));
 			oDontExpandStrings = false;
 		}
-		| T_OP_ROUND '(' const ')'		{ rpn_Number(&$$, math_Round($3)); }
-		| T_OP_CEIL '(' const ')'		{ rpn_Number(&$$, math_Ceil($3)); }
-		| T_OP_FLOOR '(' const ')'		{ rpn_Number(&$$, math_Floor($3)); }
-		| T_OP_FDIV '(' const comma const ')'	{ rpn_Number(&$$, math_Div($3, $5)); }
-		| T_OP_FMUL '(' const comma const ')'	{ rpn_Number(&$$, math_Mul($3, $5)); }
-		| T_OP_SIN '(' const ')'		{ rpn_Number(&$$, math_Sin($3)); }
-		| T_OP_COS '(' const ')'		{ rpn_Number(&$$, math_Cos($3)); }
-		| T_OP_TAN '(' const ')'		{ rpn_Number(&$$, math_Tan($3)); }
-		| T_OP_ASIN '(' const ')'		{ rpn_Number(&$$, math_ASin($3)); }
-		| T_OP_ACOS '(' const ')'		{ rpn_Number(&$$, math_ACos($3)); }
-		| T_OP_ATAN '(' const ')'		{ rpn_Number(&$$, math_ATan($3)); }
-		| T_OP_ATAN2 '(' const comma const ')'	{ rpn_Number(&$$, math_ATan2($3, $5)); }
+		| T_OP_ROUND '(' const ')'
+		{
+			rpn_Number(&$$, math_Round(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_CEIL '(' const ')'
+		{
+			rpn_Number(&$$, math_Ceil(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_FLOOR '(' const ')'
+		{
+			rpn_Number(&$$, math_Floor(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_FDIV '(' const comma const ')'
+		{
+			rpn_Number(&$$,
+				   math_Div(constexpr_GetConstantValue(&$3),
+					    constexpr_GetConstantValue(&$5)));
+		}
+		| T_OP_FMUL '(' const comma const ')'
+		{
+			rpn_Number(&$$,
+				   math_Mul(constexpr_GetConstantValue(&$3),
+					    constexpr_GetConstantValue(&$5)));
+		}
+		| T_OP_SIN '(' const ')'
+		{
+			rpn_Number(&$$, math_Sin(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_COS '(' const ')'
+		{
+			rpn_Number(&$$, math_Cos(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_TAN '(' const ')'
+		{
+			rpn_Number(&$$, math_Tan(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_ASIN '(' const ')'
+		{
+			rpn_Number(&$$, math_ASin(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_ACOS '(' const ')'
+		{
+			rpn_Number(&$$, math_ACos(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_ATAN '(' const ')'
+		{
+			rpn_Number(&$$, math_ATan(constexpr_GetConstantValue(&$3)));
+		}
+		| T_OP_ATAN2 '(' const comma const ')'
+		{
+			rpn_Number(&$$,
+				   math_ATan2(constexpr_GetConstantValue(&$3),
+					      constexpr_GetConstantValue(&$5)));
+		}
 		| T_OP_STRCMP '(' string comma string ')'
 		{
 			rpn_Number(&$$, strcmp($3, $5));
@@ -1221,102 +1266,68 @@
 
 uconst		: const
 		{
-			if ($1 < 0)
-				fatalerror("Constant mustn't be negative: %d", $1);
-			$$ = $1;
+			int32_t value = constexpr_GetConstantValue(&$1);
+			if (value < 0)
+				fatalerror("Constant mustn't be negative: %d", value);
+			$$ = value;
 		}
 ;
 
-const		: T_ID					{ $$ = sym_GetConstantValue($1); }
-		| T_NUMBER				{ $$ = $1; }
-		| T_OP_HIGH '(' const ')'		{ $$ = ($3 >> 8) & 0xFF; }
-		| T_OP_LOW '(' const ')'		{ $$ = $3 & 0xFF; }
+const		: T_ID					{ constexpr_Symbol(&$$, $1); }
+		| T_NUMBER				{ constexpr_Number(&$$, $1); }
+		| T_OP_HIGH '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_LOW '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
 		| string
 		{
 			char *s = $1;
 			int32_t length = charmap_Convert(&s);
-			$$ = str2int2(s, length);
+			constexpr_Number(&$$, str2int2(s, length));
 			free(s);
 		}
-		| T_OP_LOGICNOT const %prec NEG		{ $$ = !$2; }
-		| const T_OP_LOGICOR const		{ $$ = $1 || $3; }
-		| const T_OP_LOGICAND const		{ $$ = $1 && $3; }
-		| const T_OP_LOGICEQU const		{ $$ = $1 == $3; }
-		| const T_OP_LOGICGT const		{ $$ = $1 > $3; }
-		| const T_OP_LOGICLT const		{ $$ = $1 < $3; }
-		| const T_OP_LOGICGE const		{ $$ = $1 >= $3; }
-		| const T_OP_LOGICLE const		{ $$ = $1 <= $3; }
-		| const T_OP_LOGICNE const		{ $$ = $1 != $3; }
-		| const T_OP_ADD const			{ $$ = $1 + $3; }
-		| const T_OP_SUB const			{ $$ = $1 - $3; }
-		| T_ID  T_OP_SUB T_ID
-		{
-			if (sym_IsRelocDiffDefined($1, $3) == 0)
-				fatalerror("'%s - %s' not defined.", $1, $3);
-			$$ = sym_GetDefinedValue($1) - sym_GetDefinedValue($3);
-		}
-		| const T_OP_XOR const			{ $$ = $1 ^ $3; }
-		| const T_OP_OR const			{ $$ = $1 | $3; }
-		| const T_OP_AND const			{ $$ = $1 & $3; }
-		| const T_OP_SHL const
-		{
-			if ($1 < 0)
-				warning("Left shift of negative value: %d", $1);
-
-			if ($3 < 0)
-				fatalerror("Shift by negative value: %d", $3);
-			else if ($3 >= 32)
-				fatalerror("Shift by too big value: %d", $3);
-
-			$$ = $1 << $3;
-		}
-		| const T_OP_SHR const
-		{
-			if ($3 < 0)
-				fatalerror("Shift by negative value: %d", $3);
-			else if ($3 >= 32)
-				fatalerror("Shift by too big value: %d", $3);
-
-			$$ = $1 >> $3;
-		}
-		| const T_OP_MUL const			{ $$ = $1 * $3; }
-		| const T_OP_DIV const
-		{
-			if ($3 == 0)
-				fatalerror("Division by zero");
-			$$ = $1 / $3;
-		}
-		| const T_OP_MOD const
-		{
-			if ($3 == 0)
-				fatalerror("Division by zero");
-			$$ = $1 % $3;
-		}
-		| T_OP_ADD const %prec NEG		{ $$ = +$2; }
-		| T_OP_SUB const %prec NEG		{ $$ = -$2; }
-		| T_OP_NOT const %prec NEG		{ $$ = ~$2; }
-		| T_OP_ROUND '(' const ')'		{ $$ = math_Round($3); }
-		| T_OP_CEIL '(' const ')'		{ $$ = math_Ceil($3); }
-		| T_OP_FLOOR '(' const ')'		{ $$ = math_Floor($3); }
-		| T_OP_FDIV '(' const comma const ')'	{ $$ = math_Div($3,$5); }
-		| T_OP_FMUL '(' const comma const ')'	{ $$ = math_Mul($3,$5); }
-		| T_OP_SIN '(' const ')'		{ $$ = math_Sin($3); }
-		| T_OP_COS '(' const ')'		{ $$ = math_Cos($3); }
-		| T_OP_TAN '(' const ')'		{ $$ = math_Tan($3); }
-		| T_OP_ASIN '(' const ')'		{ $$ = math_ASin($3); }
-		| T_OP_ACOS '(' const ')'		{ $$ = math_ACos($3); }
-		| T_OP_ATAN '(' const ')'		{ $$ = math_ATan($3); }
-		| T_OP_ATAN2 '(' const comma const ')'	{ $$ = math_ATan2($3,$5); }
+		| T_OP_LOGICNOT const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
+		| const T_OP_LOGICOR const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICAND const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICEQU const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICGT const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICLT const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICGE const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICLE const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_LOGICNE const		{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_ADD const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_SUB const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_XOR const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_OR const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_AND const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_SHL const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_SHR const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_MUL const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_DIV const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| const T_OP_MOD const			{ constexpr_BinaryOp(&$$, $2, &$1, &$3); }
+		| T_OP_ADD const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
+		| T_OP_SUB const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
+		| T_OP_NOT const %prec NEG		{ constexpr_UnaryOp(&$$, $1, &$2); }
+		| T_OP_ROUND '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_CEIL '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_FLOOR '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_FDIV '(' const comma const ')'	{ constexpr_BinaryOp(&$$, $1, &$3, &$5); }
+		| T_OP_FMUL '(' const comma const ')'	{ constexpr_BinaryOp(&$$, $1, &$3, &$5); }
+		| T_OP_SIN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_COS '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_TAN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_ASIN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_ACOS '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_ATAN '(' const ')'		{ constexpr_UnaryOp(&$$, $1, &$3); }
+		| T_OP_ATAN2 '(' const comma const ')'	{ constexpr_BinaryOp(&$$, $1, &$3, &$5); }
 		| T_OP_DEF {
 				oDontExpandStrings = true;
 			} '(' T_ID ')'
 		{
-			$$ = sym_isConstDefined($4);
+			constexpr_Number(&$$, sym_isConstDefined($4));
 			oDontExpandStrings = false;
 		}
 		| T_OP_STRCMP '(' string comma string ')'
 		{
-			$$ = strcmp($3, $5);
+			constexpr_Number(&$$, strcmp($3, $5));
 		}
 		| T_OP_STRIN '(' string comma string ')'
 		{
@@ -1323,11 +1334,11 @@
 			char *p = strstr($3, $5);
 
 			if (p != NULL)
-				$$ = p - $3 + 1;
+				constexpr_Number(&$$, p - $3 + 1);
 			else
-				$$ = 0;
+				constexpr_Number(&$$, 0);
 		}
-		| T_OP_STRLEN '(' string ')'		{ $$ = strlen($3); }
+		| T_OP_STRLEN '(' string ')'		{ constexpr_Number(&$$, strlen($3)); }
 		| '(' const ')'				{ $$ = $2; }
 ;
 
--- /dev/null
+++ b/src/asm/constexpr.c
@@ -1,0 +1,231 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "asm/asm.h"
+#include "asm/constexpr.h"
+#include "asm/lexer.h"
+#include "asm/main.h"
+#include "asm/mymath.h"
+#include "asm/rpn.h"
+#include "asm/symbol.h"
+
+#include "asmy.h"
+
+void constexpr_Symbol(struct ConstExpression *expr, char *tzSym)
+{
+	if (!sym_isConstant(tzSym)) {
+		struct sSymbol *pSym = sym_FindSymbol(tzSym);
+
+		if (pSym != NULL) {
+			expr->u.pSym = pSym;
+			expr->isSym = 1;
+		} else {
+			fatalerror("'%s' not defined", tzSym);
+		}
+	} else {
+		constexpr_Number(expr, sym_GetConstantValue(tzSym));
+	}
+}
+
+void constexpr_Number(struct ConstExpression *expr, int32_t i)
+{
+	expr->u.nVal = i;
+	expr->isSym = 0;
+}
+
+void constexpr_UnaryOp(struct ConstExpression *expr,
+		       int32_t op,
+		       const struct ConstExpression *src)
+{
+	if (src->isSym)
+		fatalerror("Non-constant operand in constant expression");
+
+	int32_t value = src->u.nVal;
+	int32_t result = 0;
+
+	switch (op) {
+	case T_OP_HIGH:
+		result = (value >> 8) & 0xFF;
+		break;
+	case T_OP_LOW:
+		result = value & 0xFF;
+		break;
+	case T_OP_LOGICNOT:
+		result = !value;
+		break;
+	case T_OP_ADD:
+		result = value;
+		break;
+	case T_OP_SUB:
+		result = -value;
+		break;
+	case T_OP_NOT:
+		result = ~value;
+		break;
+	case T_OP_ROUND:
+		result = math_Round(value);
+		break;
+	case T_OP_CEIL:
+		result = math_Ceil(value);
+		break;
+	case T_OP_FLOOR:
+		result = math_Floor(value);
+		break;
+	case T_OP_SIN:
+		result = math_Sin(value);
+		break;
+	case T_OP_COS:
+		result = math_Cos(value);
+		break;
+	case T_OP_TAN:
+		result = math_Tan(value);
+		break;
+	case T_OP_ASIN:
+		result = math_ASin(value);
+		break;
+	case T_OP_ACOS:
+		result = math_ACos(value);
+		break;
+	case T_OP_ATAN:
+		result = math_ATan(value);
+		break;
+	default:
+		fatalerror("Unknown unary op");
+	}
+
+	constexpr_Number(expr, result);
+}
+
+void constexpr_BinaryOp(struct ConstExpression *expr,
+			int32_t op,
+			const struct ConstExpression *src1,
+			const struct ConstExpression *src2)
+{
+	int32_t value1;
+	int32_t value2;
+	int32_t result = 0;
+
+	if (op == T_OP_SUB && src1->isSym && src2->isSym) {
+		char *symName1 = src1->u.pSym->tzName;
+		char *symName2 = src2->u.pSym->tzName;
+
+		if (!sym_IsRelocDiffDefined(symName1, symName2))
+			fatalerror("'%s - %s' not defined", symName1, symName2);
+		value1 = sym_GetDefinedValue(symName1);
+		value2 = sym_GetDefinedValue(symName2);
+		result = value1 - value2;
+	} else if (src1->isSym || src2->isSym) {
+		fatalerror("Non-constant operand in constant expression");
+	} else {
+		value1 = src1->u.nVal;
+		value2 = src2->u.nVal;
+
+		switch (op) {
+		case T_OP_LOGICOR:
+			result = value1 || value2;
+			break;
+		case T_OP_LOGICAND:
+			result = value1 && value2;
+			break;
+		case T_OP_LOGICEQU:
+			result = value1 == value2;
+			break;
+		case T_OP_LOGICGT:
+			result = value1 > value2;
+			break;
+		case T_OP_LOGICLT:
+			result = value1 < value2;
+			break;
+		case T_OP_LOGICGE:
+			result = value1 >= value2;
+			break;
+		case T_OP_LOGICLE:
+			result = value1 <= value2;
+			break;
+		case T_OP_LOGICNE:
+			result = value1 != value2;
+			break;
+		case T_OP_ADD:
+			result = value1 + value2;
+			break;
+		case T_OP_SUB:
+			result = value1 - value2;
+			break;
+		case T_OP_XOR:
+			result = value1 ^ value2;
+			break;
+		case T_OP_OR:
+			result = value1 | value2;
+			break;
+		case T_OP_AND:
+			result = value1 & value2;
+			break;
+		case T_OP_SHL:
+			if (value1 < 0)
+				warning("Left shift of negative value: %d",
+					value1);
+
+			if (value2 < 0)
+				fatalerror("Shift by negative value: %d",
+					   value2);
+			else if (value2 >= 32)
+				fatalerror("Shift by too big value: %d",
+					   value2);
+
+			result = value1 << value2;
+			break;
+		case T_OP_SHR:
+			if (value2 < 0)
+				fatalerror("Shift by negative value: %d",
+					   value2);
+			else if (value2 >= 32)
+				fatalerror("Shift by too big value: %d",
+					   value2);
+
+			result = value1 >> value2;
+			break;
+		case T_OP_MUL:
+			result = value1 * value2;
+			break;
+		case T_OP_DIV:
+			if (value2 == 0)
+				fatalerror("Division by zero");
+			result = value1 / value2;
+			break;
+		case T_OP_MOD:
+			if (value2 == 0)
+				fatalerror("Division by zero");
+			result = value1 % value2;
+			break;
+		case T_OP_FDIV:
+			result = math_Div(value1, value2);
+			break;
+		case T_OP_FMUL:
+			result = math_Mul(value1, value2);
+			break;
+		case T_OP_ATAN2:
+			result = math_ATan2(value1, value2);
+			break;
+		default:
+			fatalerror("Unknown binary op");
+		}
+	}
+
+	constexpr_Number(expr, result);
+}
+
+int32_t constexpr_GetConstantValue(struct ConstExpression *expr)
+{
+	if (expr->isSym)
+		fatalerror("Non-constant expression");
+	return expr->u.nVal;
+}
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -14,6 +14,7 @@
 #include <string.h>
 
 #include "asm/asm.h"
+#include "asm/constexpr.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
 #include "asm/rpn.h"
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -14,6 +14,7 @@
 #include <ctype.h>
 
 #include "asm/asm.h"
+#include "asm/constexpr.h"
 #include "asm/fstack.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
@@ -735,6 +736,7 @@
 
 	/* Longest match was a keyword or operator. */
 	pLexBuffer += pLongestFixed->nNameLength;
+	yylval.nConstValue = pLongestFixed->nToken;
 	return pLongestFixed->nToken;
 }