460 lines
7.9 KiB
C
460 lines
7.9 KiB
C
/* ===========================================================================
|
|
* uz80as, an assembler for the Zilog Z80 and several other microprocessors.
|
|
*
|
|
* Expression parsing.
|
|
* ===========================================================================
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "expr.h"
|
|
#include "utils.h"
|
|
#include "err.h"
|
|
#include "sym.h"
|
|
|
|
#ifndef ASSERT_H
|
|
#include <assert.h>
|
|
#endif
|
|
|
|
#ifndef CTYPE_H
|
|
#include <ctype.h>
|
|
#endif
|
|
|
|
#ifndef LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
|
|
#ifndef STDIO_H
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#ifndef STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
/* Max nested expressions. */
|
|
#define ESTKSZ 16
|
|
#define ESTKSZ2 (ESTKSZ*2)
|
|
|
|
/* Return -1 on syntax error.
|
|
* *p must be a digit already.
|
|
* *q points to one past the end of the number without suffix.
|
|
*/
|
|
static int takenum(const char *p, const char *q, int radix)
|
|
{
|
|
int k, n;
|
|
|
|
n = 0;
|
|
while (p != q) {
|
|
k = hexval(*p);
|
|
p++;
|
|
if (k >= 0 && k < radix)
|
|
n = n * radix + k;
|
|
else
|
|
return -1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* Go to the end of a number (advance all digits or letters). */
|
|
static const char *goendnum(const char *p)
|
|
{
|
|
const char *q;
|
|
|
|
for (q = p; isalnum(*q); q++)
|
|
;
|
|
return q;
|
|
}
|
|
|
|
/*
|
|
* Returns NULL on error.
|
|
* '*p' must be a digit already.
|
|
*/
|
|
static const char *getnum(const char *p, int *v)
|
|
{
|
|
int n;
|
|
char c;
|
|
const char *q;
|
|
|
|
assert(isdigit(*p));
|
|
|
|
n = 0;
|
|
q = goendnum(p) - 1;
|
|
if (isalpha(*q)) {
|
|
c = toupper(*q);
|
|
if (c == 'H') {
|
|
n = takenum(p, q, 16);
|
|
} else if (c == 'D') {
|
|
n = takenum(p, q, 10);
|
|
} else if (c == 'O') {
|
|
n = takenum(p, q, 8);
|
|
} else if (c == 'B') {
|
|
n = takenum(p, q, 2);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
n = takenum(p, q + 1, 10);
|
|
}
|
|
|
|
if (n < 0)
|
|
return NULL;
|
|
|
|
*v = n;
|
|
return q + 1;
|
|
}
|
|
|
|
/*
|
|
* Gets a number that was prefixed.
|
|
* Returns NULL on error.
|
|
*/
|
|
static const char *getpnum(const char *p, int radix, int *v)
|
|
{
|
|
const char *q;
|
|
int n;
|
|
|
|
q = goendnum(p);
|
|
n = takenum(p, q, radix);
|
|
if (n < 0)
|
|
return NULL;
|
|
*v = n;
|
|
return q;
|
|
}
|
|
|
|
/* Left shift */
|
|
static int shl(int r, int n)
|
|
{
|
|
n &= int_precission();
|
|
return r << n;
|
|
}
|
|
|
|
|
|
/* Portable arithmetic right shift. */
|
|
static int ashr(int r, int n)
|
|
{
|
|
n &= int_precission();
|
|
if (r & INT_MIN) {
|
|
return ~(~r >> n);
|
|
} else {
|
|
return r >> n;
|
|
}
|
|
}
|
|
|
|
/* Parses expression pointed by 'p'.
|
|
* If success, returns pointer to the end of parsed expression, and
|
|
* 'v' contains the calculated value of the expression.
|
|
* Returns NULL if a syntactic error has occurred.
|
|
* Operators are evaluated left to right.
|
|
* To allow precedence use parenthesis.
|
|
* 'linepc' is the program counter to consider when we find the $ current
|
|
* pointer location symbol ($).
|
|
* 'allowfr' stands for 'allow forward references'. We will issue an error
|
|
* if we find a label that is not defined.
|
|
* 'ecode' will be valid if NULL is returned. NULL can be passed as ecode.
|
|
* 'ep' is the pointer to the position where the error ocurred. NULL can be
|
|
* passed as ep.
|
|
*/
|
|
const char *expr(const char *p, int *v, int linepc, int allowfr,
|
|
enum expr_ecode *ecode, const char **ep)
|
|
{
|
|
int si, usi, usl;
|
|
const char *q;
|
|
char last;
|
|
int stack[ESTKSZ2];
|
|
int uopstk[ESTKSZ];
|
|
int r, n;
|
|
struct sym *sym;
|
|
int err;
|
|
enum expr_ecode ec;
|
|
|
|
ec = EXPR_E_NO_EXPR;
|
|
err = 0;
|
|
usi = 0;
|
|
si = 0;
|
|
r = 0;
|
|
last = 'V'; /* first void */
|
|
usl = 0;
|
|
loop:
|
|
p = skipws(p);
|
|
if (*p == '(') {
|
|
if (last == 'n') {
|
|
goto end;
|
|
} else {
|
|
if (si >= ESTKSZ2) {
|
|
eprint(_("expression too complex\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
stack[si++] = last;
|
|
stack[si++] = r;
|
|
stack[si++] = usl;
|
|
usl = usi;
|
|
p++;
|
|
r = 0;
|
|
last = 'v'; /* void */
|
|
}
|
|
} else if (*p == ')') {
|
|
if (last != 'n') {
|
|
ec = EXPR_E_CPAR;
|
|
goto esyntax;
|
|
} else if (si == 0) {
|
|
goto end;
|
|
} else {
|
|
p++;
|
|
n = r;
|
|
usl = stack[--si];
|
|
r = stack[--si];
|
|
last = (char) stack[--si];
|
|
goto oper;
|
|
}
|
|
} else if (*p == '+') {
|
|
p++;
|
|
if (last == 'n')
|
|
last = '+';
|
|
} else if (*p == '-') {
|
|
if (last == 'n') {
|
|
p++;
|
|
last = '-';
|
|
} else {
|
|
goto uoper;
|
|
}
|
|
} else if (*p == '~') {
|
|
goto uoper;
|
|
} else if (*p == '!') {
|
|
if (*(p + 1) == '=') {
|
|
if (last != 'n') {
|
|
ec = EXPR_E_OPER;
|
|
goto esyntax;
|
|
} else {
|
|
p += 2;
|
|
last = 'N';
|
|
}
|
|
} else {
|
|
goto uoper;
|
|
}
|
|
} else if (*p == '*') {
|
|
if (last == 'n') {
|
|
last = *p++;
|
|
} else {
|
|
p++;
|
|
n = linepc;
|
|
goto oper;
|
|
}
|
|
} else if (*p == '/' || *p == '&' || *p == '|'
|
|
|| *p == '^')
|
|
{
|
|
if (last != 'n') {
|
|
ec = EXPR_E_OPER;
|
|
goto esyntax;
|
|
} else {
|
|
last = *p++;
|
|
}
|
|
} else if (*p == '>') {
|
|
if (last != 'n') {
|
|
ec = EXPR_E_OPER;
|
|
goto esyntax;
|
|
}
|
|
p++;
|
|
if (*p == '=') {
|
|
last = 'G';
|
|
p++;
|
|
} else if (*p == '>') {
|
|
last = 'R';
|
|
p++;
|
|
} else {
|
|
last = '>';
|
|
}
|
|
} else if (*p == '<') {
|
|
if (last != 'n') {
|
|
ec = EXPR_E_OPER;
|
|
goto esyntax;
|
|
}
|
|
p++;
|
|
if (*p == '=') {
|
|
last = 'S';
|
|
p++;
|
|
} else if (*p == '<') {
|
|
last = 'L';
|
|
p++;
|
|
} else {
|
|
last = '<';
|
|
}
|
|
} else if (*p == '=') {
|
|
if (last != 'n') {
|
|
ec = EXPR_E_OPER;
|
|
goto esyntax;
|
|
}
|
|
p++;
|
|
if (*p == '=')
|
|
p++;
|
|
last = '=';
|
|
} else if (*p == '\'') {
|
|
if (last == 'n')
|
|
goto end;
|
|
p++;
|
|
n = *p++;
|
|
if (*p != '\'') {
|
|
ec = EXPR_E_CHAR;
|
|
goto esyntax;
|
|
}
|
|
p++;
|
|
goto oper;
|
|
} else if (*p == '$') {
|
|
if (last == 'n')
|
|
goto end;
|
|
p++;
|
|
if (hexval(*p) < 0) {
|
|
n = linepc;
|
|
goto oper;
|
|
}
|
|
q = getpnum(p, 16, &n);
|
|
if (q == NULL) {
|
|
p--;
|
|
ec = EXPR_E_HEX;
|
|
goto esyntax;
|
|
}
|
|
p = q;
|
|
goto oper;
|
|
} else if (*p == '@') {
|
|
if (last == 'n')
|
|
goto end;
|
|
p++;
|
|
q = getpnum(p, 8, &n);
|
|
if (q == NULL) {
|
|
p--;
|
|
ec = EXPR_E_OCTAL;
|
|
goto esyntax;
|
|
}
|
|
p = q;
|
|
goto oper;
|
|
} else if (*p == '%') {
|
|
if (last == 'n') {
|
|
last = *p;
|
|
p++;
|
|
} else {
|
|
p++;
|
|
q = getpnum(p, 2, &n);
|
|
if (q == NULL) {
|
|
ec = EXPR_E_BIN;
|
|
goto esyntax;
|
|
}
|
|
p = q;
|
|
goto oper;
|
|
}
|
|
} else if ((p[0] == '0') && (p[1] == 'x')) {
|
|
p+=2;
|
|
q = getpnum(p, 16, &n);
|
|
if (q == NULL) {
|
|
p--;
|
|
ec = EXPR_E_HEX;
|
|
goto esyntax;
|
|
}
|
|
p = q;
|
|
goto oper;
|
|
} else if (isdigit(*p)) {
|
|
if (last == 'n')
|
|
goto end;
|
|
q = getnum(p, &n);
|
|
if (q == NULL) {
|
|
ec = EXPR_E_DEC;
|
|
goto esyntax;
|
|
}
|
|
p = q;
|
|
goto oper;
|
|
} else if (isidc0(*p)) {
|
|
if (last == 'n')
|
|
goto end;
|
|
q = p;
|
|
while (isidc(*p))
|
|
p++;
|
|
sym = lookup(q, p, 0, 0);
|
|
if (sym == NULL) {
|
|
n = 0;
|
|
if (!allowfr) {
|
|
err = 1;
|
|
eprint(_("undefined label"));
|
|
epchars(q, p);
|
|
enl();
|
|
newerr();
|
|
}
|
|
} else {
|
|
n = sym->val;
|
|
}
|
|
goto oper;
|
|
} else if (last == 'V') {
|
|
goto esyntax;
|
|
} else if (last != 'n') {
|
|
ec = EXPR_E_SYNTAX;
|
|
goto esyntax;
|
|
} else {
|
|
end: if (v != NULL)
|
|
*v = r;
|
|
return p;
|
|
}
|
|
goto loop;
|
|
uoper:
|
|
if (last == 'n')
|
|
goto end;
|
|
if (usi >= ESTKSZ) {
|
|
eprint(_("expression too complex\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
uopstk[usi++] = *p++;
|
|
goto loop;
|
|
oper:
|
|
while (usi > usl) {
|
|
usi--;
|
|
switch (uopstk[usi]) {
|
|
case '~': n = ~n; break;
|
|
case '-': n = -n; break;
|
|
case '!': n = !n; break;
|
|
}
|
|
}
|
|
switch (last) {
|
|
case 'V': r = n; break;
|
|
case 'v': r = n; break;
|
|
case '+': r += n; break;
|
|
case '-': r -= n; break;
|
|
case '*': r *= n; break;
|
|
case '&': r &= n; break;
|
|
case '|': r |= n; break;
|
|
case '^': r ^= n; break;
|
|
case '=': r = r == n; break;
|
|
case '<': r = r < n; break;
|
|
case '>': r = r > n ; break;
|
|
case 'G': r = r >= n; break;
|
|
case 'S': r = r <= n; break;
|
|
case 'N': r = r != n; break;
|
|
/* This would be logical right shift:
|
|
* case 'R': r = (unsigned int) r >> n; break;
|
|
*/
|
|
case 'R': r = ashr(r, n); break;
|
|
case 'L': r = shl(r, n); break;
|
|
case '~': r = ~n; break;
|
|
case '%':
|
|
if (n != 0) {
|
|
r %= n;
|
|
} else if (!err && !allowfr) {
|
|
err = 1;
|
|
eprint(_("modulo by zero\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case '/':
|
|
if (n != 0) {
|
|
r /= n;
|
|
} else if (!err && !allowfr) {
|
|
err = 1;
|
|
eprint(_("division by zero\n"));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
}
|
|
last = 'n';
|
|
goto loop;
|
|
esyntax:
|
|
if (ecode != NULL)
|
|
*ecode = ec;
|
|
if (ep != NULL)
|
|
*ep = p;
|
|
return NULL;
|
|
}
|