/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer
 *  Copyright (C) 2019 Tibor 'Igor2' Palinkas
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/camv-rnd
 *    lead developer: http://repo.hu/projects/camv-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "gcode_vm.h"
#include "gcode_lex.h"

int gcodeerror(gcode_prg_t *ctx, const char *msg)
{
	char tmp[256];
	if (!ctx->subseq_error) {
		sprintf(tmp, "gcode error at %ld:%ld: ", ctx->line, ctx->col);
		ctx->error(ctx, 0, tmp);
		ctx->subseq_error = 1;
	}
	ctx->error(ctx, 0, msg);
	return 0;
}

/* Most of the lexer code is borrowed from fawk (and is relicensed by the author */
#define append(chr, bad) \
do { \
	if (ctx->used >= ctx->alloced) { \
		char *bf; \
		ctx->alloced += 256; \
		if ((bf = realloc(ctx->buff, ctx->alloced)) == NULL) { ctx->alloced = 0; bad; }  \
		ctx->buff = bf; \
	} \
	ctx->buff[ctx->used++] = chr; \
} while(0)

static int getch(gcode_prg_t *ctx)
{
	int ret;
	if (ctx->pushback > 0) {
		ret = ctx->pushback;
		ctx->pushback = -1;
	}
	else
		ret = ctx->get_char(ctx);
	if (ret == EOF) ctx->in_eof = 1;
	else if (ret == '\n') { ctx->line++; ctx->col = 0; }
	else { ctx->col++; }
	return ret;
}

static void ungetch(gcode_prg_t *ctx, int chr)
{
	assert(ctx->pushback <= 0);
	ctx->pushback = chr;
	if (chr == '\n') { ctx->line--; ctx->col = 1000; }
	else ctx->col--;
}

#define del_last() ctx->used--

static int read_numeric(gcode_prg_t *ctx, YYSTYPE *lval, int had_decimal_dot)
{
	int chr, chr2, had_e = 0;

	next:;
	chr = getch(ctx); append(chr, return -1);
	if (isdigit(chr)) goto next;
	if ((chr == '.') && (!had_decimal_dot)) { had_decimal_dot = 1; goto next; }
	if (((chr == 'e') || (chr == 'E')) && (!had_e)) { /* 1.2e-5 */
		had_e = 1;
		chr = getch(ctx); append(chr, return -1);
		if (isdigit(chr)) goto next;
		if ((chr == '+') || (chr == '-')) {
			chr2 = getch(ctx);
			if (isdigit(chr2)) { append(chr2, return -1); goto next; }
			gcodeerror(ctx, "invalid numeric: e+ or e- must be followed by a digit");
			return -1;
		}
		gcodeerror(ctx, "invalid numeric: e must be followed by sign or digit");
		return -1;
	}
	ungetch(ctx, chr); del_last();
	append('\0', return -1);
	lval->num = strtod(ctx->buff, NULL);

	return had_decimal_dot ? T_NUM : T_DEC;
}



int gcodelex_(YYSTYPE *lval, gcode_prg_t *ctx)
{
	int chr;
	char tmp[128];

	restart:;

	if (ctx->in_eof) goto handle_eof;

	/* start of a token: read the next non-space character */
	do { chr = getch(ctx); } while((chr == ' ') || (chr == '\t'));

	ctx->used = 0;

	if (isalpha(chr)) { /* read a code or math function name */
		append(toupper(chr), return -1);
		for(;;) {
			chr = getch(ctx);
			if (!isalpha(chr)) {
				ungetch(ctx, chr);
				break;
			}
			append(toupper(chr), return -1);
		}
		if (ctx->used == 1) {
			if (ctx->buff[0] == 'N')
				return T_LINENO;
			lval->chr = ctx->buff[0];
			return T_CHR; /* single letter code */
		}
		append('\0', return -1); /* append a virtual \0 so it's easier to strcmp */
		if      (strcmp(ctx->buff, "SIN") == 0)        return T_SIN;
		else if (strcmp(ctx->buff, "COS") == 0)        return T_COS;
		else if (strcmp(ctx->buff, "TAN") == 0)        return T_TAN;
		else if (strcmp(ctx->buff, "ASIN") == 0)       return T_ASIN;
		else if (strcmp(ctx->buff, "ACOS") == 0)       return T_ACOS;
		else if (strcmp(ctx->buff, "ATAN") == 0)       return T_ATAN;
		else if (strcmp(ctx->buff, "ABS") == 0)        return T_ABS;
		else if (strcmp(ctx->buff, "FIX") == 0)        return T_FIX;
		else if (strcmp(ctx->buff, "FUP") == 0)        return T_FUP;
		else if (strcmp(ctx->buff, "EXP") == 0)        return T_EXP;
		else if (strcmp(ctx->buff, "LN") == 0)         return T_LN;
		else if (strcmp(ctx->buff, "ROUND") == 0)      return T_ROUND;
		else if (strcmp(ctx->buff, "SQRT") == 0)       return T_SQRT;
		else {
			gcodeerror(ctx, "Invalid code or math function:");
			gcodeerror(ctx, ctx->buff);
			gcodeerror(ctx, "\n");
			return -1;
		}
	}
	else if (isdigit(chr)) { /* read a number */
		append(chr, return -1);
		return read_numeric(ctx, lval, 0);
	}
	else switch(chr) {
		case '[': case ']': case '*': case '/': case '+': case '-': case '#': case'=':
			return chr;

		case '(': /* read comment */
			for(;;) {
				chr = getch(ctx);
				if (chr == ')')
					goto restart;
				if (chr == '\n') {
					gcodeerror(ctx, "Newline in comment");
					return -1;
				}
				if (chr == EOF) {
					gcodeerror(ctx, "EOF in comment");
					return -1;
				}
			}

		case '.': /* number starting with a decimal point */
			append('.', return -1);
			return read_numeric(ctx, lval, 1);
			break;

		case '\n':
			for(;;) {
				chr = getch(ctx);
				if (!isspace(chr)) {
					ungetch(ctx, chr);
					return T_NL;
				}
			}

		case EOF:
			handle_eof:;
			ctx->in_eof = 1;
			return EOF;

		default:
			sprintf(tmp, "Invalid character on input: '%c'\n", chr);
			gcodeerror(ctx, tmp);
			return -1;
	}
}

int gcodelex(YYSTYPE *lval, gcode_prg_t *ctx)
{
	int t = gcodelex_(lval, ctx);
/*	printf("* %d\n", t);*/
	return t;
}

