Parser generating bytecode and renderer interpreting it.

This commit is contained in:
Scott Duensing 2022-11-30 19:14:39 -06:00
parent 544b19da61
commit ffe9b5224d
4 changed files with 626 additions and 487 deletions

View file

@ -33,6 +33,7 @@ set(SOURCE_FILES
src/array.c
src/draw.c
src/image.c
src/vecparse.c
)
add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES})

61
include/vecparse.h Normal file
View file

@ -0,0 +1,61 @@
/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef VECPARSE_H
#define VECPARSE_H
enum ParserKeywordE {
PARSE_NONE = 0,
PARSE_BOX,
PARSE_CIRCLE,
PARSE_CLEAR,
PARSE_COLOR,
PARSE_COMMENT,
PARSE_ELLIPSE,
PARSE_FILL,
PARSE_LINE,
PARSE_PALETTE,
PARSE_PLOT,
PARSE_RECTANGLE,
PARSE_RESET
};
typedef enum ParserKeywordE ParserKeywordT;
typedef struct PointS {
int x;
int y;
} PointT;
typedef struct VecByteCodeS {
unsigned char *bytes;
int length;
int bufferSize;
} VecByteCodeT;
int vecparser(char *programIn, VecByteCodeT *bytecode);
#endif // VECPARSE_H

425
src/vecparse.c Normal file
View file

@ -0,0 +1,425 @@
/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "common.h"
#include "vecparse.h"
typedef struct CommandsS {
char *command;
ParserKeywordT keyword;
} CommandsT;
static void ensureBufferSize(VecByteCodeT *bytecode, int needed);
static void outputByte(VecByteCodeT *bytecode, unsigned char byte);
static void outputWord(VecByteCodeT *bytecode, unsigned short word);
static gboolean parserGetNextValue(char *token, char **valueEnd, int *x);
static gboolean parserGetWord(char *word, char **tokenEnd);
static gboolean parserGetX(char **tokenEnd, int *x);
static gboolean parserGetXY(char **tokenEnd, int *x, int *y);
static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z);
static void ensureBufferSize(VecByteCodeT *bytecode, int needed) {
unsigned char *temp = NULL;
if (bytecode->bufferSize < bytecode->length + needed) {
bytecode->bufferSize += 1024;
temp = realloc(bytecode->bytes, bytecode->bufferSize);
if (temp == NULL) {
//***TODO*** Something bad happened.
} else {
bytecode->bytes = temp;
}
}
}
static void outputByte(VecByteCodeT *bytecode, unsigned char byte) {
ensureBufferSize(bytecode, 1);
bytecode->bytes[bytecode->length++] = byte;
}
static void outputWord(VecByteCodeT *bytecode, unsigned short word) {
ensureBufferSize(bytecode, 2);
bytecode->bytes[bytecode->length++] = (word & 0xFF00) >> 8;
bytecode->bytes[bytecode->length++] = word & 0x00FF;
}
static gboolean parserGetNextValue(char *token, char **valueEnd, int *x) {
char *value;
char *endPtr = NULL;
// Return next value in a comma separated list.
//***TODO*** Variable support.
value = strtok_r(token, ",", valueEnd);
if (value == NULL) return FALSE;
errno = 0; endPtr = NULL;
*x = (int)strtol(value, &endPtr, 10);
if (errno != 0 || *endPtr != 0) return FALSE;
return TRUE;
}
static gboolean parserGetWord(char *word, char **tokenEnd) {
char *token;
// Is this token the "WORD"?
token = strtok_r(NULL, " ", tokenEnd);
if (token == NULL) return FALSE;
if (strcasecmp(token, word) != 0) return FALSE;
return TRUE;
}
static gboolean parserGetX(char **tokenEnd, int *x) {
char *value;
char *endPtr = NULL;
// Return single value.
//***TODO*** Variable support.
value = strtok_r(NULL, " ", tokenEnd);
if (value == NULL) return FALSE;
errno = 0; endPtr = NULL;
*x = (int)strtol(value, &endPtr, 10);
if (errno != 0 || *endPtr != 0) return FALSE;
return TRUE;
}
static gboolean parserGetXY(char **tokenEnd, int *x, int *y) {
char *token;
char *valueEnd;
// Return values of X,Y pair.
token = strtok_r(NULL, " ", tokenEnd);
if (token == NULL) return FALSE;
if (!parserGetNextValue(token, &valueEnd, x)) return FALSE;
if (!parserGetNextValue(NULL, &valueEnd, y)) return FALSE;
return TRUE;
}
static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z) {
char *token;
char *valueEnd;
// Return values of X,Y pair.
token = strtok_r(NULL, " ", tokenEnd);
if (token == NULL) return FALSE;
if (!parserGetNextValue(token, &valueEnd, x)) return FALSE;
if (!parserGetNextValue(NULL, &valueEnd, y)) return FALSE;
if (!parserGetNextValue(NULL, &valueEnd, z)) return FALSE;
return TRUE;
}
int vecparser(char *programIn, VecByteCodeT *bytecode) {
int command;
char *line;
char *lineEnd;
gboolean lineOkay;
gboolean isOkay;
char *token;
char *tokenEnd;
int lineNumber;
int x1;
int y1;
int x2;
int y2;
PointT p1;
PointT p2;
PointT *points = NULL;
int result = -1; // Returns -1 on success or line number of first error.
CommandsT commands[] = {
{ "BOX", PARSE_BOX },
{ "CIRCLE", PARSE_CIRCLE },
{ "CLEAR", PARSE_CLEAR },
{ "COLOR", PARSE_COLOR },
{ "//", PARSE_COMMENT },
{ "ELLIPSE", PARSE_ELLIPSE },
{ "FILL", PARSE_FILL },
{ "LINE", PARSE_LINE },
{ "PALETTE", PARSE_PALETTE },
{ "PLOT", PARSE_PLOT },
{ "RECTANGLE", PARSE_RECTANGLE },
{ "RESET", PARSE_RESET },
{ NULL, PARSE_NONE }
};
// Parse code.
lineNumber = 0;
line = strtok_r(programIn, "\n", &lineEnd);
while (line != NULL) {
// Get the first token on the line.
token = strtok_r(line, " ", &tokenEnd);
// Is this something we care about?
command = 0;
lineOkay = FALSE;
while (commands[command].command) {
if (strcasecmp(commands[command].command, token) == 0) {
// Yep! Gather arguments and generate bytecode.
switch (commands[command].keyword) {
case PARSE_NONE:
// Won't happen, but silences an error.
break;
case PARSE_BOX:
// Box (value),(value) to (value),(value)
if (!parserGetXY(&tokenEnd, &x1, &y1)) break;
if (!parserGetWord("TO", &tokenEnd)) break;
if (!parserGetXY(&tokenEnd, &x2, &y2)) break;
if (x2 < x1 || x1 < 0 || x2 < 0 || x1 > 319 || x2 > 319) break;
if (y2 < y1 || y1 < 0 || y2 < 0 || y1 > 199 || y2 > 199) break;
outputByte(bytecode, PARSE_BOX);
outputWord(bytecode, x1);
outputWord(bytecode, y1);
outputWord(bytecode, x2);
outputWord(bytecode, y2);
lineOkay = TRUE;
break;
case PARSE_CIRCLE:
// Circle (value) at (value),(value)
if (!parserGetX(&tokenEnd, &y2)) break;
if (!parserGetWord("AT", &tokenEnd)) break;
if (!parserGetXY(&tokenEnd, &x1, &y1)) break;
if (x1 < 0 || x1 > 319) break;
if (y1 < 0 || y1 > 199) break;
outputByte(bytecode, PARSE_CIRCLE);
outputWord(bytecode, y2);
outputWord(bytecode, x1);
outputWord(bytecode, y1);
lineOkay = TRUE;
break;
case PARSE_CLEAR:
outputByte(bytecode, PARSE_CLEAR);
lineOkay = TRUE;
break;
case PARSE_COLOR:
// Color (short)
if (!parserGetX(&tokenEnd, &x1)) break;
if (x1 < 0 || x1 > 15) break;
outputByte(bytecode, PARSE_COLOR);
outputByte(bytecode, x1);
lineOkay = TRUE;
break;
case PARSE_COMMENT:
// Eat the rest of the line.
while (token != NULL) {
token = strtok_r(NULL, " ", &tokenEnd);
}
lineOkay = TRUE;
break;
case PARSE_ELLIPSE:
// Ellipse (value),(value) to (value),(value)
if (!parserGetXY(&tokenEnd, &x1, &y1)) break;
if (!parserGetWord("TO", &tokenEnd)) break;
if (!parserGetXY(&tokenEnd, &x2, &y2)) break;
if (x2 < x1 || x1 < 0 || x2 < 0 || x1 > 319 || x2 > 319) break;
if (y2 < y1 || y1 < 0 || y2 < 0 || y1 > 199 || y2 > 199) break;
outputByte(bytecode, PARSE_ELLIPSE);
outputWord(bytecode, x1);
outputWord(bytecode, y1);
outputWord(bytecode, x2);
outputWord(bytecode, y2);
lineOkay = TRUE;
break;
case PARSE_FILL:
// Fill (value),(value) {to (value}
if (!parserGetXY(&tokenEnd, &x1, &y1)) break;
if (x1 < 0 || x1 > 319) break;
if (y1 < 0 || y1 > 199) break;
// Do they want to fill to a certain color? Or over the current color?
x2 = 16; // 16 == Fill, otherwise FillTo
if (parserGetWord("TO", &tokenEnd)) {
if (!parserGetX(&tokenEnd, &x2)) break;
if (x2 < 0 || x2 > 15) break;
}
outputByte(bytecode, PARSE_FILL);
outputWord(bytecode, x1);
outputWord(bytecode, y1);
outputByte(bytecode, x2);
lineOkay = TRUE;
break;
case PARSE_LINE:
// Line (value),(value) to (value),(value) [to ...]
points = NULL;
if (!parserGetXY(&tokenEnd, &p1.x, &p1.y)) break;
if (!parserGetWord("TO", &tokenEnd)) break;
if (!parserGetXY(&tokenEnd, &p2.x, &p2.y)) break;
if (p1.x < 0 || p1.x > 319) break;
if (p1.y < 0 || p1.y > 199) break;
if (p2.x < 0 || p2.x > 319) break;
if (p2.y < 0 || p2.y > 199) break;
arrput(points, p1);
arrput(points, p2);
isOkay = TRUE;
while (parserGetWord("TO", &tokenEnd)) {
if (!parserGetXY(&tokenEnd, &p1.x, &p1.y)) {
// Error. Unwind array.
while (arrlen(points) > 0) {
arrdel(points, 0);
}
arrfree(points);
isOkay = FALSE;
break;
}
arrput(points, p1);
}
if (isOkay) {
outputByte(bytecode, PARSE_LINE);
outputWord(bytecode, arrlen(points));
for (x1=0; x1<arrlen(points); x1++) {
outputWord(bytecode, points[x1].x);
outputWord(bytecode, points[x1].y);
}
// Unwind array.
while (arrlen(points) > 0) {
arrdel(points, 0);
}
arrfree(points);
lineOkay = TRUE;
}
break;
case PARSE_PALETTE:
// Palette (short) AS (short),(short),(short)
if (!parserGetX(&tokenEnd, &x1)) break;
if (!parserGetWord("AS", &tokenEnd)) break;
if (!parserGetXYZ(&tokenEnd, &x2, &y1, &y2)) break;
if (x1 < 0 || x1 > 15) break;
if (x2 < 0 || x2 > 15) break;
if (y1 < 0 || y1 > 15) break;
if (y2 < 0 || y2 > 15) break;
outputByte(bytecode, PARSE_PALETTE);
outputByte(bytecode, x1);
outputByte(bytecode, x2);
outputByte(bytecode, y1);
outputByte(bytecode, y2);
lineOkay = TRUE;
break;
case PARSE_PLOT:
// Plot (value),(value)
if (!parserGetXY(&tokenEnd, &x1, &y1)) break;
if (x1 < 0 || x1 > 319) break;
if (y1 < 0 || y1 > 199) break;
outputByte(bytecode, PARSE_PLOT);
outputWord(bytecode, x1);
outputWord(bytecode, y1);
lineOkay = TRUE;
break;
case PARSE_RECTANGLE:
// Rectangle (value),(value) to (value),(value)
if (!parserGetXY(&tokenEnd, &x1, &y1)) break;
if (!parserGetWord("TO", &tokenEnd)) break;
if (!parserGetXY(&tokenEnd, &x2, &y2)) break;
if (x2 < x1 || x1 < 0 || x2 < 0 || x1 > 319 || x2 > 319) break;
if (y2 < y1 || y1 < 0 || y2 < 0 || y1 > 199 || y2 > 199) break;
outputByte(bytecode, PARSE_RECTANGLE);
outputWord(bytecode, x1);
outputWord(bytecode, y1);
outputWord(bytecode, x2);
outputWord(bytecode, y2);
lineOkay = TRUE;
break;
case PARSE_RESET:
outputByte(bytecode, PARSE_RESET);
lineOkay = TRUE;
break;
} // switch
// Stop looking for this command - we handled it.
break;
} else {
// Keep looking until we find this command or run out of commands.
command++;
} // command match
} // loop over commands
// Is everything still okay?
if (!lineOkay) {
// Nope - error.
result = lineNumber;
break;
}
// Move to next line.
line = strtok_r(NULL, "\n", &lineEnd);
lineNumber++;
} // read program line
/*
* (value) is a 16-bit integer. Since we only need a fraction of the
* possible values provided by this, we steal a couple bits for our
* own use. All values are stored without messing with 2's complement.
*
* Type Negative Value
* \ /__________/_
* \ // \
* tnvvvvvvvvvvvvvv
*
* So with this scheme we can store values from -16383 to 16383 (yes,
* zero is represented twice).
*
* The Type bit determines if the value stored is a literal value or a
* reference to a variable in the variable table.
*
* (short) is a simplified version used for colors. It is always positive
* and has a range from 0 to 127 with Type being the MSb. This effectively
* limits the number of available variables to 128.
*
*/
return result;
}

View file

@ -34,6 +34,7 @@
#include "utils.h"
#include "draw.h"
#include "image.h"
#include "vecparse.h"
#define SSM(m, w, l) scintilla_send_message(self->sci, m, w, l)
@ -44,13 +45,6 @@
#define PREVIEW_HEIGHT 400
enum PassE {
PASS_DRAW,
PASS_GENERATE
};
typedef enum PassE PassT;
typedef struct VectorDataS {
WindowDataT windowData;
GtkWidget *drawVectorImage;
@ -68,18 +62,6 @@ typedef struct VectorDataS {
} VectorDataT;
typedef struct CommandsS {
char *command;
gboolean (*parserFunction)(PassT pass, char **tokenEnd, VectorDataT *self);
} CommandsT;
typedef struct PointS {
int x;
int y;
} PointT;
static int _nextEditorId = 0;
@ -88,24 +70,7 @@ EVENT void drawVectorImageClick(GtkWidget *object, GdkEventButton *event, g
EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData);
EVENT void fileVectorTraceImageFileSet(GtkWidget *object, gpointer userData);
EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData);
static gboolean parseBox(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseCircle(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseClear(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseColor(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseComment(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseEllipse(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseFill(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseLine(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parsePalette(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parsePlot(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseRectangle(PassT pass, char **tokenEnd, VectorDataT *self);
static gboolean parseReset(PassT pass, char **tokenEnd, VectorDataT *self);
static void parser(PassT pass, gpointer userData);
static gboolean parserGetNextValue(char *token, char **valueEnd, int *x);
static gboolean parserGetWord(char *word, char **tokenEnd);
static gboolean parserGetX(char **tokenEnd, int *x);
static gboolean parserGetXY(char **tokenEnd, int *x, int *y);
static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z);
static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self);
EVENT void scaleVectorTraceImageValueChanged(GtkWidget *object, gpointer userData);
EVENT gboolean winVectorClose(GtkWidget *object, gpointer userData);
static void winVectorDelete(gpointer userData);
@ -183,8 +148,11 @@ EVENT void drawVectorImageClick(GtkWidget *object, GdkEventButton *event, gpoint
EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotification *notifyData, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
int lineNumber = (int)SSM(SCI_LINEFROMPOSITION, (uptr_t)notifyData->position, (sptr_t)0);
VectorDataT *self = (VectorDataT *)userData;
int lineNumber = (int)SSM(SCI_LINEFROMPOSITION, (uptr_t)notifyData->position, (sptr_t)0);
int length = SSM(SCI_GETLENGTH, 0, 0);
char *code;
VecByteCodeT byteCode;
(void)sciWidget;
(void)ctrlID;
@ -194,10 +162,36 @@ EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotifi
switch (notifyData->nmhdr.code) {
case SCN_MODIFIED:
if (notifyData->modificationType & SC_MOD_INSERTTEXT || notifyData->modificationType & SC_MOD_DELETETEXT) {
// Allocate space to fetch code from editor.
code = (char *)malloc(length + 1);
if (!code) return;
// Clear error markers.
SSM(SCI_MARKERDELETEALL, MARKER_ERROR_ARROW, 0);
SSM(SCI_MARKERDELETEALL, MARKER_ERROR_HIGHLIGHT, 0);
// Fetch code.
SSM(SCI_GETTEXT, length, (sptr_t)code);
// Parse code.
parser(PASS_DRAW, userData);
// Refresh widget.
gtk_widget_queue_draw(self->drawVectorImage);
byteCode.bytes = NULL;
byteCode.length = 0;
byteCode.bufferSize = 0;
lineNumber = vecparser(code, &byteCode);
if (lineNumber >= 0) {
// Mark lines that fail to parse.
SSM(SCI_MARKERADD, lineNumber, MARKER_ERROR_ARROW);
SSM(SCI_MARKERADD, lineNumber, MARKER_ERROR_HIGHLIGHT);
} else {
// All good!
renderBytecode(&byteCode, self);
//***DEBUG***
FILE *out = fopen("bytecode.bin", "wb");
fwrite(byteCode.bytes, byteCode.length, 1, out);
fclose(out);
}
// Release bytecode.
if (byteCode.bytes != NULL) DEL(byteCode.bytes);
// Release code.
DEL(code);
// Mark text dirty. SCN_SAVEPOINTLEFT isn't being reliable.
self->windowData.isDirty = TRUE;
}
@ -280,475 +274,133 @@ EVENT void menuVectorFileClose(GtkWidget *object, gpointer userData) {
}
static gboolean parseBox(PassT pass, char **tokenEnd, VectorDataT *self) {
static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
int index = 0;
int x1;
int y1;
int x2;
int y2;
// Box (value),(value) to (value),(value)
if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE;
if (!parserGetWord("TO", tokenEnd)) return FALSE;
if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE;
if (x2 < x1 || x1 < 0 || x2 < 0 || x1 > 319 || x2 > 319) return FALSE;
if (y2 < y1 || y1 < 0 || y2 < 0 || y1 > 199 || y2 > 199) return FALSE;
switch (pass) {
case PASS_DRAW:
jlDrawBox(self->jlc, x1, y1, x2, y2);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseCircle(PassT pass, char **tokenEnd, VectorDataT *self) {
int r;
int x1;
int y1;
// Circle (value) at (value),(value)
if (!parserGetX(tokenEnd, &r)) return FALSE;
if (!parserGetWord("AT", tokenEnd)) return FALSE;
if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE;
if (x1 < 0 || x1 > 319) return FALSE;
if (y1 < 0 || y1 > 199) return FALSE;
switch (pass) {
case PASS_DRAW:
jlDrawCircle(self->jlc, x1, y1, r);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseClear(PassT pass, char **tokenEnd, VectorDataT *self) {
switch (pass) {
case PASS_DRAW:
jlDrawClear(self->jlc);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseColor(PassT pass, char **tokenEnd, VectorDataT *self) {
int c;
// Color (short)
if (!parserGetX(tokenEnd, &c)) return FALSE;
if (c < 0 || c > 15) return FALSE;
switch (pass) {
case PASS_DRAW:
jlDrawColorSet(self->jlc, c);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseComment(PassT pass, char **tokenEnd, VectorDataT *self) {
char *token = (char *)1; // Just can't be NULL.
// Eat the rest of the line.
while (token != NULL) {
token = strtok_r(NULL, " ", tokenEnd);
}
return TRUE;
}
static gboolean parseEllipse(PassT pass, char **tokenEnd, VectorDataT *self) {
int x1;
int y1;
int x2;
int y2;
// Ellipse (value),(value) to (value),(value)
if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE;
if (!parserGetWord("TO", tokenEnd)) return FALSE;
if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE;
if (x2 < x1 || x1 < 0 || x2 < 0 || x1 > 319 || x2 > 319) return FALSE;
if (y2 < y1 || y1 < 0 || y2 < 0 || y1 > 199 || y2 > 199) return FALSE;
switch (pass) {
case PASS_DRAW:
jlDrawEllipse(self->jlc, x1, y1, x2, y2);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseFill(PassT pass, char **tokenEnd, VectorDataT *self) {
int c = 16;
int x1;
int y1;
// Fill (value),(value) {to (value}
if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE;
if (x1 < 0 || x1 > 319) return FALSE;
if (y1 < 0 || y1 > 199) return FALSE;
// Do they want to fill to a certain color? Or over the current color?
if (parserGetWord("TO", tokenEnd)) {
if (!parserGetX(tokenEnd, &c)) return FALSE;
if (c < 0 || c > 15) return FALSE;
}
switch (pass) {
case PASS_DRAW:
if (c > 15) {
jlDrawFill(self->jlc, x1, y1);
} else {
jlDrawFillTo(self->jlc, x1, y1, c);
}
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseLine(PassT pass, char **tokenEnd, VectorDataT *self) {
int x;
PointT p1;
PointT p2;
PointT *points = NULL;
// Line (value),(value) to (value),(value) [to ...]
if (!parserGetXY(tokenEnd, &p1.x, &p1.y)) return FALSE;
if (!parserGetWord("TO", tokenEnd)) return FALSE;
if (!parserGetXY(tokenEnd, &p2.x, &p2.y)) return FALSE;
if (p1.x < 0 || p1.x > 319) return FALSE;
if (p1.y < 0 || p1.y > 199) return FALSE;
if (p2.x < 0 || p2.x > 319) return FALSE;
if (p2.y < 0 || p2.y > 199) return FALSE;
arrput(points, p1);
arrput(points, p2);
while (parserGetWord("TO", tokenEnd)) {
if (!parserGetXY(tokenEnd, &p1.x, &p1.y)) {
// Error. Unwind array.
while (arrlen(points) > 0) arrdel(points, 0);
arrfree(points);
return FALSE;
}
arrput(points, p1);
}
switch (pass) {
case PASS_DRAW:
for (x=0; x<arrlen(points)-1; x++) {
jlDrawLine(self->jlc, points[x].x, points[x].y, points[x+1].x, points[x+1].y);
}
break;
case PASS_GENERATE:
break;
}
// Unwind array.
while (arrlen(points) > 0) arrdel(points, 0);
arrfree(points);
return TRUE;
}
static gboolean parsePalette(PassT pass, char **tokenEnd, VectorDataT *self) {
int count;
int i;
int r;
int g;
int b;
if (!parserGetX(tokenEnd, &i)) return FALSE;
if (!parserGetWord("AS", tokenEnd)) return FALSE;
if (!parserGetXYZ(tokenEnd, &r, &g, &b)) return FALSE;
if (i < 0 || i > 15) return FALSE;
if (r < 0 || r > 15) return FALSE;
if (g < 0 || g > 15) return FALSE;
if (b < 0 || b > 15) return FALSE;
#define GET_BYTE (bytecode->bytes[index++])
#define GET_WORD ((bytecode->bytes[index++] << 8) + bytecode->bytes[index++])
switch (pass) {
case PASS_DRAW:
jlPaletteSet(self->jlc, i, r, g, b);
break;
while (index < bytecode->length) {
switch (bytecode->bytes[index++]) {
case PASS_GENERATE:
break;
}
case PARSE_NONE:
// Won't happen, but silences an error.
break;
return TRUE;
}
case PARSE_BOX:
x1 = GET_WORD;
y1 = GET_WORD;
x2 = GET_WORD;
y2 = GET_WORD;
printf("Box %d,%d to %d,%d\n", x1, y1, x2, y2);
jlDrawBox(self->jlc, x1, y1, x2, y2);
break;
case PARSE_CIRCLE:
y2 = GET_WORD;
x1 = GET_WORD;
y1 = GET_WORD;
printf("Circle %d at %d,%d\n", y2, x1, y1);
jlDrawCircle(self->jlc, x1, y1, y2);
break;
static gboolean parsePlot(PassT pass, char **tokenEnd, VectorDataT *self) {
int x1;
int y1;
case PARSE_CLEAR:
printf("Clear\n");
jlDrawClear(self->jlc);
break;
// Plot (value),(value)
if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE;
if (x1 < 0 || x1 > 319) return FALSE;
if (y1 < 0 || y1 > 199) return FALSE;
case PARSE_COLOR:
x1 = GET_BYTE;
printf("Color %d\n", x1);
jlDrawColorSet(self->jlc, x1);
break;
switch (pass) {
case PASS_DRAW:
jlDrawPixelSet(self->jlc, x1, y1);
break;
case PARSE_COMMENT:
// Nothing to do!
break;
case PASS_GENERATE:
break;
}
case PARSE_ELLIPSE:
x1 = GET_WORD;
y1 = GET_WORD;
x2 = GET_WORD;
y2 = GET_WORD;
printf("Ellipse %d,%d to %d,%d\n", x1, y1, x2, y2);
jlDrawEllipse(self->jlc, x1, y1, x2, y2);
break;
return TRUE;
}
static gboolean parseRectangle(PassT pass, char **tokenEnd, VectorDataT *self) {
int x1;
int y1;
int x2;
int y2;
// Rectangle (value),(value) to (value),(value)
if (!parserGetXY(tokenEnd, &x1, &y1)) return FALSE;
if (!parserGetWord("TO", tokenEnd)) return FALSE;
if (!parserGetXY(tokenEnd, &x2, &y2)) return FALSE;
if (x2 < x1 || x1 < 0 || x2 < 0 || x1 > 319 || x2 > 319) return FALSE;
if (y2 < y1 || y1 < 0 || y2 < 0 || y1 > 199 || y2 > 199) return FALSE;
switch (pass) {
case PASS_DRAW:
jlDrawBoxFilled(self->jlc, x1, y1, x2, y2);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static gboolean parseReset(PassT pass, char **tokenEnd, VectorDataT *self) {
// Reset
// Reset draw context.
jlContextDel(&self->jlc);
self->jlc = jlContextNew(cairo_image_surface_get_data(self->surface));
switch (pass) {
case PASS_DRAW:
jlDrawColorSet(self->jlc, 0);
jlDrawClear(self->jlc);
jlDrawColorSet(self->jlc, 15);
break;
case PASS_GENERATE:
break;
}
return TRUE;
}
static void parser(PassT pass, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
int length = SSM(SCI_GETLENGTH, 0, 0);
int x;
int lineNumber = 0;
char *code;
char *line;
char *lineEnd;
gboolean lineOkay;
char *token;
char *tokenEnd;
CommandsT commands[] = {
{ "BOX", parseBox },
{ "CIRCLE", parseCircle },
{ "CLEAR", parseClear },
{ "COLOR", parseColor },
{ "//", parseComment },
{ "ELLIPSE", parseEllipse },
{ "FILL", parseFill },
{ "LINE", parseLine },
{ "PALETTE", parsePalette },
{ "PLOT", parsePlot },
{ "RECTANGLE", parseRectangle },
{ "RESET", parseReset },
{ NULL, NULL }
};
// Allocate space to fetch code from editor.
code = (char *)malloc(length + 1);
if (!code) return;
// Fetch code.
SSM(SCI_GETTEXT, length, (sptr_t)code);
if (pass == PASS_DRAW) {
// Clear error markers.
SSM(SCI_MARKERDELETEALL, MARKER_ERROR_ARROW, 0);
SSM(SCI_MARKERDELETEALL, MARKER_ERROR_HIGHLIGHT, 0);
}
// Parse code.
line = strtok_r(code, "\n", &lineEnd);
while (line != NULL) {
// Get the first token on the line.
token = strtok_r(line, " ", &tokenEnd);
// Is this something we care about?
x = 0;
lineOkay = FALSE;
while (commands[x].command) {
if (strcasecmp(commands[x].command, token) == 0) {
if (commands[x].parserFunction(pass, &tokenEnd, self) == TRUE) {
lineOkay = TRUE;
case PARSE_FILL:
x1 = GET_WORD;
y1 = GET_WORD;
x2 = GET_BYTE;
if (x2 > 15) {
printf("Fill %d,%d\n", x1, y1);
jlDrawFill(self->jlc, x1, y1);
} else {
printf("Fill %d,%d to %d\n", x1, y1, x2);
jlDrawFillTo(self->jlc, x1, y1, x2);
}
break;
}
x++;
}
if (lineOkay == FALSE && pass == PASS_DRAW) {
// Mark lines that fail to parse.
SSM(SCI_MARKERADD, lineNumber, MARKER_ERROR_ARROW);
SSM(SCI_MARKERADD, lineNumber, MARKER_ERROR_HIGHLIGHT);
}
// Move to next line.
line = strtok_r(NULL, "\n", &lineEnd);
lineNumber++;
}
// Release code.
free(code);
case PARSE_LINE:
count = GET_WORD;
x1 = GET_WORD;
y1 = GET_WORD;
printf("Line %d,%d", x1, y1);
for (i=0; i<count - 1; i++) {
x2 = GET_WORD;
y2 = GET_WORD;
printf(" to %d,%d", x2, y2);
jlDrawLine(self->jlc, x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
printf("\n");
break;
/*
* (value) is a 16-bit integer. Since we only need a fraction of the
* possible values provided by this, we steal a couple bits for our
* own use. All values are stored without messing with 2's complement.
*
* Type Negative Value
* \ /__________/_
* \ // \
* tnvvvvvvvvvvvvvv
*
* So with this scheme we can store values from -16383 to 16383 (yes,
* zero is represented twice).
*
* The Type bit determines if the value stored is a literal value or a
* reference to a variable in the variable table.
*
* (short) is a simplified version used for colors. It is always positive
* and has a range from 0 to 127 with Type being the MSb. This effectively
* limits the number of available variables to 128.
*
*/
}
case PARSE_PALETTE:
x1 = GET_BYTE;
x2 = GET_BYTE;
y1 = GET_BYTE;
y2 = GET_BYTE;
printf("Palette %d as %d,%d,%d\n", x1, x2, y1, y2);
jlPaletteSet(self->jlc, x1, x2, y1, y2);
break;
case PARSE_PLOT:
x1 = GET_WORD;
y1 = GET_WORD;
printf("Plot %d,%d\n", x1, y1);
jlDrawPixelSet(self->jlc, x1, y1);
break;
static gboolean parserGetNextValue(char *token, char **valueEnd, int *x) {
char *value;
char *endPtr = NULL;
case PARSE_RECTANGLE:
x1 = GET_WORD;
y1 = GET_WORD;
x2 = GET_WORD;
y2 = GET_WORD;
printf("Rectangle %d,%d to %d,%d\n", x1, y1, x2, y2);
jlDrawBoxFilled(self->jlc, x1, y1, x2, y2);
break;
// Return next value in a comma separated list.
//***TODO*** Variable support.
case PARSE_RESET:
printf("Reset\n");
jlPaletteDefault(self->jlc);
jlDrawColorSet(self->jlc, 0);
jlDrawClear(self->jlc);
jlDrawColorSet(self->jlc, 15);
break;
value = strtok_r(token, ",", valueEnd);
if (value == NULL) return FALSE;
errno = 0; endPtr = NULL;
*x = (int)strtol(value, &endPtr, 10);
if (errno != 0) return FALSE;
} // switch
} // while
return TRUE;
}
static gboolean parserGetWord(char *word, char **tokenEnd) {
char *token;
// Is this token the "WORD"?
token = strtok_r(NULL, " ", tokenEnd);
if (token == NULL) return FALSE;
if (strcasecmp(token, word) != 0) return FALSE;
return TRUE;
}
static gboolean parserGetX(char **tokenEnd, int *x) {
char *value;
char *endPtr = NULL;
// Return single value.
//***TODO*** Variable support.
value = strtok_r(NULL, " ", tokenEnd);
if (value == NULL) return FALSE;
errno = 0; endPtr = NULL;
*x = (int)strtol(value, &endPtr, 10);
if (errno != 0) return FALSE;
return TRUE;
}
static gboolean parserGetXY(char **tokenEnd, int *x, int *y) {
char *token;
char *valueEnd;
// Return values of X,Y pair.
token = strtok_r(NULL, " ", tokenEnd);
if (token == NULL) return FALSE;
if (!parserGetNextValue(token, &valueEnd, x)) return FALSE;
if (!parserGetNextValue(NULL, &valueEnd, y)) return FALSE;
return TRUE;
}
static gboolean parserGetXYZ(char **tokenEnd, int *x, int *y, int *z) {
char *token;
char *valueEnd;
// Return values of X,Y pair.
token = strtok_r(NULL, " ", tokenEnd);
if (token == NULL) return FALSE;
if (!parserGetNextValue(token, &valueEnd, x)) return FALSE;
if (!parserGetNextValue(NULL, &valueEnd, y)) return FALSE;
if (!parserGetNextValue(NULL, &valueEnd, z)) return FALSE;
return TRUE;
// Refresh widget.
gtk_widget_queue_draw(self->drawVectorImage);
}