cleanups and more test cases
This commit is contained in:
+7
-3
@@ -1,5 +1,9 @@
|
|||||||
cache
|
cache/
|
||||||
vgcore.*
|
report/
|
||||||
libnoja.a
|
|
||||||
noja
|
noja
|
||||||
|
!noja/
|
||||||
|
|
||||||
|
vgcore.*
|
||||||
*.gcov
|
*.gcov
|
||||||
|
lib*.a
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
./build.sh --coverage
|
|
||||||
mkdir temp
|
|
||||||
export LLVM_PROFILE_FILE="temp/noja.profraw"
|
|
||||||
./build/noja run examples/json.noja
|
|
||||||
llvm-profdata merge -sparse temp/noja.profraw -o temp/noja.profdata
|
|
||||||
llvm-cov show ./build/noja -instr-profile=temp/noja.profdata >> build/coverage.txt
|
|
||||||
llvm-cov export ./build/noja -instr-profile=temp/noja.profdata >> build/coverage.json
|
|
||||||
rm -rf temp
|
|
||||||
@@ -12,12 +12,14 @@ copy:
|
|||||||
PUSHINT 0;
|
PUSHINT 0;
|
||||||
ASS "i";
|
ASS "i";
|
||||||
POP 1;
|
POP 1;
|
||||||
|
|
||||||
|
loop_start:
|
||||||
PUSHVAR "i";
|
PUSHVAR "i";
|
||||||
PUSHVAR "L";
|
PUSHVAR "L";
|
||||||
PUSHVAR "count";
|
PUSHVAR "count";
|
||||||
CALL 1, 1;
|
CALL 1, 1;
|
||||||
LSS;
|
LSS;
|
||||||
JUMPIFNOTANDPOP 31;
|
JUMPIFNOTANDPOP loop_end;
|
||||||
PUSHVAR "L";
|
PUSHVAR "L";
|
||||||
PUSHVAR "i";
|
PUSHVAR "i";
|
||||||
SELECT;
|
SELECT;
|
||||||
@@ -30,7 +32,8 @@ copy:
|
|||||||
ADD;
|
ADD;
|
||||||
ASS "i";
|
ASS "i";
|
||||||
POP 1;
|
POP 1;
|
||||||
JUMP 12;
|
JUMP loop_start;
|
||||||
|
loop_end:
|
||||||
PUSHVAR "L2";
|
PUSHVAR "L2";
|
||||||
RETURN 1;
|
RETURN 1;
|
||||||
RETURN 0;
|
RETURN 0;
|
||||||
@@ -50,7 +53,7 @@ bubble_sort:
|
|||||||
PUSHNNE;
|
PUSHNNE;
|
||||||
EQL;
|
EQL;
|
||||||
JUMPIFNOTANDPOP default_less_cb_end;
|
JUMPIFNOTANDPOP default_less_cb_end;
|
||||||
PUSHFUN 50, 2;
|
PUSHFUN default_less_cb, 2;
|
||||||
ASS "less";
|
ASS "less";
|
||||||
POP 1;
|
POP 1;
|
||||||
JUMP default_less_cb_end;
|
JUMP default_less_cb_end;
|
||||||
|
|||||||
@@ -18,14 +18,22 @@ SRCDIR = src
|
|||||||
# when doing `make clean`.
|
# when doing `make clean`.
|
||||||
OBJDIR = cache
|
OBJDIR = cache
|
||||||
|
|
||||||
|
# Temporary directory where execution coverage
|
||||||
|
# reports are stored. If this directory doesn't
|
||||||
|
# exist, it will be created. This folder is
|
||||||
|
# deleted by `make clean`.
|
||||||
|
REPORTDIR = report
|
||||||
|
|
||||||
# Source directory for each program to be build
|
# Source directory for each program to be build
|
||||||
# They are relative to the SRCDIR folder.
|
# They are relative to the SRCDIR folder.
|
||||||
LIB_SUBDIR = noja
|
LIB_SUBDIR = lib
|
||||||
CLI_SUBDIR = cli
|
CLI_SUBDIR = cli
|
||||||
|
TST_SUBDIR = test
|
||||||
|
|
||||||
# Resulting file names
|
# Resulting file names
|
||||||
LIB_FNAME = libnoja.a
|
LIB_FNAME = libnoja.a
|
||||||
CLI_FNAME = noja
|
CLI_FNAME = noja
|
||||||
|
TST_FNAME = test
|
||||||
|
|
||||||
# Where the build programs will be moved when
|
# Where the build programs will be moved when
|
||||||
# installation occurres.
|
# installation occurres.
|
||||||
@@ -92,12 +100,14 @@ endif
|
|||||||
# Absolute path of each program's source tree
|
# Absolute path of each program's source tree
|
||||||
LIB_SRCDIR = $(SRCDIR)/$(LIB_SUBDIR)
|
LIB_SRCDIR = $(SRCDIR)/$(LIB_SUBDIR)
|
||||||
CLI_SRCDIR = $(SRCDIR)/$(CLI_SUBDIR)
|
CLI_SRCDIR = $(SRCDIR)/$(CLI_SUBDIR)
|
||||||
|
TST_SRCDIR = $(SRCDIR)/$(TST_SUBDIR)
|
||||||
|
|
||||||
# Each program that is being build uses as object
|
# Each program that is being build uses as object
|
||||||
# cache a subfolder of OBJDIR called like the it's
|
# cache a subfolder of OBJDIR called like the it's
|
||||||
# source tree base folder in SRCDIR.
|
# source tree base folder in SRCDIR.
|
||||||
LIB_OBJDIR = $(OBJDIR)/$(LIB_SUBDIR)
|
LIB_OBJDIR = $(OBJDIR)/$(LIB_SUBDIR)
|
||||||
CLI_OBJDIR = $(OBJDIR)/$(CLI_SUBDIR)
|
CLI_OBJDIR = $(OBJDIR)/$(CLI_SUBDIR)
|
||||||
|
TST_OBJDIR = $(OBJDIR)/$(TST_SUBDIR)
|
||||||
|
|
||||||
LIB_CFILES = $(call rwildcard, $(LIB_SRCDIR), *.c)
|
LIB_CFILES = $(call rwildcard, $(LIB_SRCDIR), *.c)
|
||||||
LIB_HFILES = $(call rwildcard, $(LIB_SRCDIR), *.h)
|
LIB_HFILES = $(call rwildcard, $(LIB_SRCDIR), *.h)
|
||||||
@@ -107,15 +117,24 @@ CLI_CFILES = $(call rwildcard, $(CLI_SRCDIR), *.c)
|
|||||||
CLI_HFILES = $(call rwildcard, $(CLI_SRCDIR), *.h)
|
CLI_HFILES = $(call rwildcard, $(CLI_SRCDIR), *.h)
|
||||||
CLI_OFILES = $(patsubst $(CLI_SRCDIR)/%.c, $(CLI_OBJDIR)/%.o, $(CLI_CFILES))
|
CLI_OFILES = $(patsubst $(CLI_SRCDIR)/%.c, $(CLI_OBJDIR)/%.o, $(CLI_CFILES))
|
||||||
|
|
||||||
|
TST_CFILES = $(call rwildcard, $(TST_SRCDIR), *.c)
|
||||||
|
TST_HFILES = $(call rwildcard, $(TST_SRCDIR), *.h)
|
||||||
|
TST_OFILES = $(patsubst $(TST_SRCDIR)/%.c, $(TST_OBJDIR)/%.o, $(TST_CFILES))
|
||||||
|
|
||||||
|
ALL_CFILES = $(call rwildcard, $(SRCDIR), *.c)
|
||||||
|
|
||||||
# Useful abbreviations
|
# Useful abbreviations
|
||||||
LIB = $(OUTDIR)/$(LIB_FNAME)
|
LIB = $(OUTDIR)/$(LIB_FNAME)
|
||||||
CLI = $(OUTDIR)/$(CLI_FNAME)
|
CLI = $(OUTDIR)/$(CLI_FNAME)
|
||||||
|
TST = $(OUTDIR)/$(TST_FNAME)
|
||||||
|
|
||||||
# ===================== #
|
# ===================== #
|
||||||
# === Rules =========== #
|
# === Rules =========== #
|
||||||
# ===================== #
|
# ===================== #
|
||||||
|
|
||||||
all: $(LIB) $(CLI)
|
.PHONY: report install clean all
|
||||||
|
|
||||||
|
all: $(LIB) $(CLI) $(TST)
|
||||||
|
|
||||||
$(OBJDIR)/%.o: $(SRCDIR)/%.c
|
$(OBJDIR)/%.o: $(SRCDIR)/%.c
|
||||||
@ mkdir -p $(@D)
|
@ mkdir -p $(@D)
|
||||||
@@ -128,11 +147,29 @@ $(LIB): $(LIB_OFILES)
|
|||||||
$(CLI): $(CLI_OFILES) $(LIB)
|
$(CLI): $(CLI_OFILES) $(LIB)
|
||||||
$(CC) $^ -o $@ $(LFLAGS)
|
$(CC) $^ -o $@ $(LFLAGS)
|
||||||
|
|
||||||
|
$(TST): $(TST_OFILES) $(LIB)
|
||||||
|
gcc $^ -o $@ $(LFLAGS)
|
||||||
|
|
||||||
|
#$(REPORTDIR)/%.gcov: src/%.c
|
||||||
|
# @ mkdir -p $(@D)
|
||||||
|
# gcov --stdout $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.c, $^) > $@
|
||||||
|
#
|
||||||
|
#report: $(patsubst $(SRCDIR)/%.c, $(REPORTDIR)/%.gcov, $(ALL_CFILES))
|
||||||
|
# lcov --capture --directory $(OBJDIR) --output-file coverage.info
|
||||||
|
# genhtml coverage.info --output-directory .
|
||||||
|
#
|
||||||
|
|
||||||
|
report:
|
||||||
|
lcov --capture --directory $(OBJDIR) --output-file $(OBJDIR)/coverage.info
|
||||||
|
@ mkdir -p report
|
||||||
|
genhtml $(OBJDIR)/coverage.info --output-directory report
|
||||||
|
|
||||||
install: $(LIB) $(CLI)
|
install: $(LIB) $(CLI)
|
||||||
sudo cp $(LIB) $(LIB_INSTALLDIR)
|
sudo cp $(LIB) $(LIB_INSTALLDIR)
|
||||||
sudo cp $(CLI) $(CLI_INSTALLDIR)
|
sudo cp $(CLI) $(CLI_INSTALLDIR)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(OBJDIR)
|
rm -rf $(OBJDIR)
|
||||||
|
rm -rf $(REPORTDIR)
|
||||||
rm -f $(LIB)
|
rm -f $(LIB)
|
||||||
rm -f $(CLI)
|
rm -f $(CLI)
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
make -B BUILD_MODE=COVERAGE CC=gcc
|
||||||
|
./test tests/suite
|
||||||
|
make report
|
||||||
|
firefox report/index.html
|
||||||
+1
-1
@@ -32,7 +32,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "../noja/noja.h"
|
#include "../lib/noja.h"
|
||||||
|
|
||||||
static const char usage[] =
|
static const char usage[] =
|
||||||
"Usage patterns:\n"
|
"Usage patterns:\n"
|
||||||
|
|||||||
@@ -0,0 +1,393 @@
|
|||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "../utils/error.h"
|
||||||
|
#include "../utils/source.h"
|
||||||
|
#include "../utils/labellist.h"
|
||||||
|
#include "../common/executable.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *str;
|
||||||
|
size_t len;
|
||||||
|
size_t cur;
|
||||||
|
} Context;
|
||||||
|
|
||||||
|
static void skipIdentifier(Context *ctx)
|
||||||
|
{
|
||||||
|
while(ctx->cur < ctx->len && (isalpha(ctx->str[ctx->cur]) || isdigit(ctx->str[ctx->cur]) || ctx->str[ctx->cur] == '_'))
|
||||||
|
ctx->cur += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skipSpaces(Context *ctx)
|
||||||
|
{
|
||||||
|
while(ctx->cur < ctx->len && isspace(ctx->str[ctx->cur]))
|
||||||
|
ctx->cur += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t offset;
|
||||||
|
size_t length;
|
||||||
|
} Slice;
|
||||||
|
|
||||||
|
static bool parseLabelAndOpcode(Context *ctx, bool *no_label,
|
||||||
|
Slice *label, Slice *opcode,
|
||||||
|
Error *error)
|
||||||
|
{
|
||||||
|
assert(ctx != NULL && no_label != NULL
|
||||||
|
&& label != NULL && opcode != NULL);
|
||||||
|
|
||||||
|
// NOTE: This function must start at
|
||||||
|
// the first byte of the label or
|
||||||
|
// opcode. All whitespace must be
|
||||||
|
// consumed by the caller.
|
||||||
|
|
||||||
|
// Now we expect either a label and an
|
||||||
|
// opcode, or just an opcode
|
||||||
|
//
|
||||||
|
// <label>: <opcode> ..operands..
|
||||||
|
// <opcode> ..operands..
|
||||||
|
//
|
||||||
|
// Labels and opcodes can bot contain
|
||||||
|
// digits, letters and underscores.
|
||||||
|
|
||||||
|
Slice label_or_opcode;
|
||||||
|
{
|
||||||
|
char c = ctx->str[ctx->cur];
|
||||||
|
if(!isalpha(c) && c != '_') {
|
||||||
|
// ERROR: Missing opcode
|
||||||
|
Error_Report(error, 0, "Missing opcode");
|
||||||
|
}
|
||||||
|
|
||||||
|
label_or_opcode.offset = ctx->cur;
|
||||||
|
skipIdentifier(ctx);
|
||||||
|
label_or_opcode.length = ctx->cur - label_or_opcode.offset;
|
||||||
|
|
||||||
|
assert(label_or_opcode.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the character after the label or opcode
|
||||||
|
// (ignoring whitespace), and if it's a ':',
|
||||||
|
// then it was a label.
|
||||||
|
skipSpaces(ctx);
|
||||||
|
if(ctx->cur < ctx->len && ctx->str[ctx->cur] == ':') {
|
||||||
|
|
||||||
|
// Skip the ':' and the whitespace after it.
|
||||||
|
ctx->cur += 1;
|
||||||
|
skipSpaces(ctx);
|
||||||
|
|
||||||
|
if(ctx->cur == ctx->len || (!isalpha(ctx->str[ctx->cur]) && ctx->str[ctx->cur] != '_')) {
|
||||||
|
Error_Report(error, 0, "Missing opcode after label");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the opcode is expected.
|
||||||
|
opcode->offset = ctx->cur;
|
||||||
|
skipIdentifier(ctx);
|
||||||
|
opcode->length = ctx->cur - opcode->offset;
|
||||||
|
assert(opcode->length > 0);
|
||||||
|
|
||||||
|
*no_label = false;
|
||||||
|
*label = label_or_opcode;
|
||||||
|
skipSpaces(ctx);
|
||||||
|
} else {
|
||||||
|
*no_label = true;
|
||||||
|
*opcode = label_or_opcode;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseStringOperand(Context *ctx, Error *error, BPAlloc *alloc, Operand *op)
|
||||||
|
{
|
||||||
|
// Skip the first double quote.
|
||||||
|
assert(ctx->cur < ctx->len && ctx->str[ctx->cur] == '"');
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
size_t literal_offset = ctx->cur;
|
||||||
|
|
||||||
|
// For now do a dumb copy into the buffer
|
||||||
|
// without considering special characters.
|
||||||
|
while(ctx->cur < ctx->len && ctx->str[ctx->cur] != '"')
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
if(ctx->cur == ctx->len) {
|
||||||
|
Error_Report(error, 0, "End of source inside a string literal");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t literal_length = ctx->cur - literal_offset;
|
||||||
|
|
||||||
|
// Skip the ending double quotes.
|
||||||
|
assert(ctx->cur < ctx->len && ctx->str[ctx->cur] == '"');
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
char *copy = BPAlloc_Malloc(alloc, literal_length+1);
|
||||||
|
if(copy == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(copy, ctx->str + literal_offset, literal_length);
|
||||||
|
copy[literal_length] = '\0';
|
||||||
|
|
||||||
|
op->type = OPTP_STRING;
|
||||||
|
op->as_string = copy;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseIntegerOperand(Context *ctx, Error *error, Operand *op)
|
||||||
|
{
|
||||||
|
// It's ensured by the caller that the cursor is
|
||||||
|
// pointing to a sequence of one or more digits
|
||||||
|
// NOT followed by a dot and a digit after that
|
||||||
|
// (so this is an integer for sure, not a float).
|
||||||
|
assert(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
long long int buffer = 0;
|
||||||
|
do {
|
||||||
|
// Transform each digit into it's integer value.
|
||||||
|
int d = ctx->str[ctx->cur] - '0';
|
||||||
|
assert(d >= 0 && d <= 9);
|
||||||
|
|
||||||
|
// Will this overflow?
|
||||||
|
if(buffer > (LLONG_MAX - d) / 10) {
|
||||||
|
Error_Report(error, 0, "Integer literal is too big to be represented in %d bits", 8*sizeof(buffer));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = buffer * 10 + d;
|
||||||
|
ctx->cur += 1;
|
||||||
|
} while(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
// Not a float!
|
||||||
|
|
||||||
|
op->type = OPTP_INT;
|
||||||
|
op->as_int = buffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseFloatingOperand(Context *ctx, Error *error, Operand *op)
|
||||||
|
{
|
||||||
|
(void) error; // At the moment this function doesn't report
|
||||||
|
// any error. This may change when overflows
|
||||||
|
// and underflows are detected.
|
||||||
|
|
||||||
|
// It's ensured by the caller that the cursor is
|
||||||
|
// pointing to a sequence of one or more digits
|
||||||
|
// followed by a dot and a digit after that.
|
||||||
|
assert(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
double buffer = 0;
|
||||||
|
do {
|
||||||
|
// Transform each digit into it's integer value.
|
||||||
|
int d = ctx->str[ctx->cur] - '0';
|
||||||
|
assert(d >= 0 && d <= 9);
|
||||||
|
|
||||||
|
// Should overflow be checked?
|
||||||
|
buffer = buffer * 10 + d;
|
||||||
|
|
||||||
|
ctx->cur += 1;
|
||||||
|
} while(ctx->str[ctx->cur] != '.');
|
||||||
|
|
||||||
|
assert(ctx->cur+1 < ctx->len && ctx->str[ctx->cur] == '.' && isdigit(ctx->str[ctx->cur+1]));
|
||||||
|
|
||||||
|
// Skip the dot.
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
double q = 1;
|
||||||
|
do {
|
||||||
|
// Transform each digit into it's integer value.
|
||||||
|
int d = ctx->str[ctx->cur] - '0';
|
||||||
|
assert(d >= 0 && d <= 9);
|
||||||
|
|
||||||
|
q /= 10;
|
||||||
|
buffer += q * d;
|
||||||
|
|
||||||
|
ctx->cur += 1;
|
||||||
|
} while(ctx->cur < ctx->len && isdigit(ctx->str[ctx->cur]));
|
||||||
|
|
||||||
|
op->type = OPTP_FLOAT;
|
||||||
|
op->as_float = buffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseOperands(Context *ctx, BPAlloc *alloc, Error *error,
|
||||||
|
LabelList *list, Operand *opv, int *opc, int opc_max)
|
||||||
|
{
|
||||||
|
// NOTE: The whitespace before the first operand must
|
||||||
|
// be consumed by the caller.
|
||||||
|
|
||||||
|
if(ctx->cur < ctx->len && ctx->str[ctx->cur] != ';')
|
||||||
|
while(1) {
|
||||||
|
|
||||||
|
Operand op;
|
||||||
|
|
||||||
|
char c = ctx->str[ctx->cur];
|
||||||
|
if(c == '"') {
|
||||||
|
|
||||||
|
if(!parseStringOperand(ctx, error, alloc, &op))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if(isdigit(c)) {
|
||||||
|
|
||||||
|
/* Integer or float operand */
|
||||||
|
|
||||||
|
size_t k = ctx->cur;
|
||||||
|
while(k < ctx->len && isdigit(ctx->str[k]))
|
||||||
|
k += 1;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
if(k+1 >= ctx->len || ctx->str[k] != '.' || !isdigit(ctx->str[k+1]))
|
||||||
|
ok = parseIntegerOperand(ctx, error, &op);
|
||||||
|
else
|
||||||
|
ok = parseFloatingOperand(ctx, error, &op);
|
||||||
|
|
||||||
|
if(!ok)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if(isalpha(c) || c == '_') {
|
||||||
|
|
||||||
|
/* Label */
|
||||||
|
size_t offset = ctx->cur;
|
||||||
|
skipIdentifier(ctx);
|
||||||
|
size_t length = ctx->cur - offset;
|
||||||
|
|
||||||
|
Promise *promise = LabelList_GetLabel(list, ctx->str + offset, length);
|
||||||
|
if(promise == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
op.type = OPTP_PROMISE;
|
||||||
|
op.as_promise = promise;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ERROR: Unexpected character
|
||||||
|
Error_Report(error, 0, "Unexpected character '%c'", c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(*opc == opc_max) {
|
||||||
|
Error_Report(error, 0, "Too many operands");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
opv[*opc] = op;
|
||||||
|
*opc += 1;
|
||||||
|
// Now prepare for the next operand
|
||||||
|
skipSpaces(ctx);
|
||||||
|
|
||||||
|
if(ctx->cur == ctx->len || ctx->str[ctx->cur] == ';')
|
||||||
|
break;
|
||||||
|
|
||||||
|
c = ctx->str[ctx->cur];
|
||||||
|
if(c != ',') {
|
||||||
|
Error_Report(error, 0, "Unexpected character '%c' (',' or ';' were expected)", c);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the comma
|
||||||
|
ctx->cur += 1;
|
||||||
|
|
||||||
|
// Skip the spaces before the next operand
|
||||||
|
skipSpaces(ctx);
|
||||||
|
}
|
||||||
|
assert(ctx->cur == ctx->len || ctx->str[ctx->cur] == ';');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Executable *assemble(Source *src, Error *error)
|
||||||
|
{
|
||||||
|
Executable *exe = NULL;
|
||||||
|
|
||||||
|
BPAlloc *alloc = BPAlloc_Init(-1);
|
||||||
|
if(alloc == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelList *list = LabelList_New(alloc);
|
||||||
|
if(list == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExeBuilder *builder = ExeBuilder_New(alloc);
|
||||||
|
if(builder == NULL) {
|
||||||
|
Error_Report(error, 1, "No memory");
|
||||||
|
LabelList_Free(list);
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context ctx = {
|
||||||
|
.str = Source_GetBody(src),
|
||||||
|
.len = Source_GetSize(src),
|
||||||
|
.cur = 0,
|
||||||
|
};
|
||||||
|
while(1) {
|
||||||
|
|
||||||
|
skipSpaces(&ctx);
|
||||||
|
|
||||||
|
if(ctx.cur == ctx.len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
bool no_label;
|
||||||
|
Slice label, opcode_name;
|
||||||
|
|
||||||
|
if(!parseLabelAndOpcode(&ctx, &no_label, &label, &opcode_name, error))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// If a label was defined, add it to the list.
|
||||||
|
if(no_label == false) {
|
||||||
|
long long int value = ExeBuilder_InstrCount(builder);
|
||||||
|
if(!LabelList_SetLabel(list, ctx.str + label.offset, label.length, value)) {
|
||||||
|
Error_Report(error, 1, "Out of memory");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the opcode is valid (at this
|
||||||
|
// point it's just an unchecked identifier)
|
||||||
|
Opcode opcode;
|
||||||
|
const char *name = ctx.str + opcode_name.offset;
|
||||||
|
if(!Executable_GetOpcodeBinaryFromName(name, opcode_name.length, &opcode)) {
|
||||||
|
Error_Report(error, 0, "Opcode %.*s doesn't exist", (int) opcode_name.length, name);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse operands */
|
||||||
|
// (whitespace was already skipped)
|
||||||
|
|
||||||
|
Operand opv[8];
|
||||||
|
int opc = 0;
|
||||||
|
|
||||||
|
if(!parseOperands(&ctx, alloc, error, list, opv, &opc, sizeof(opv)/sizeof(opv[0])))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
// The operand list ended with a ';' or the
|
||||||
|
// end of the file. If the file didn't end,
|
||||||
|
// then the ';' must be consumed.
|
||||||
|
assert(ctx.cur == ctx.len || ctx.str[ctx.cur] == ';');
|
||||||
|
if(ctx.cur < ctx.len) ctx.cur += 1;
|
||||||
|
|
||||||
|
if(!ExeBuilder_Append(builder, error, opcode, opv, opc, opcode_name.offset, opcode_name.length))
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t unresolved_count = LabelList_GetUnresolvedCount(list);
|
||||||
|
if(unresolved_count > 0) {
|
||||||
|
Error_Report(error, 0, "%d unresolved labels", unresolved_count);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
exe = ExeBuilder_Finalize(builder, error);
|
||||||
|
|
||||||
|
if(exe != NULL)
|
||||||
|
(void) Executable_SetSource(exe, src);
|
||||||
|
|
||||||
|
done:
|
||||||
|
LabelList_Free(list);
|
||||||
|
BPAlloc_Free(alloc);
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef ASSEMBLE_H
|
||||||
|
#define ASSEMBLE_H
|
||||||
|
#include "../utils/error.h"
|
||||||
|
#include "../utils/source.h"
|
||||||
|
#include "../common/executable.h"
|
||||||
|
Executable *assemble(Source *src, Error *error);
|
||||||
|
#endif /* ASSEMBLE_H */
|
||||||
@@ -87,7 +87,7 @@ static const InstrInfo instr_table[] = {
|
|||||||
[OPCODE_LEQ] = {"LEQ", 0, NULL},
|
[OPCODE_LEQ] = {"LEQ", 0, NULL},
|
||||||
[OPCODE_GEQ] = {"GEQ", 0, NULL},
|
[OPCODE_GEQ] = {"GEQ", 0, NULL},
|
||||||
[OPCODE_AND] = {"AND", 0, NULL},
|
[OPCODE_AND] = {"AND", 0, NULL},
|
||||||
[OPCODE_OR] = {"AND", 0, NULL},
|
[OPCODE_OR] = {"OR", 0, NULL},
|
||||||
|
|
||||||
[OPCODE_ASS] = {"ASS", 1, (OperandType[]) {OPTP_STRING}},
|
[OPCODE_ASS] = {"ASS", 1, (OperandType[]) {OPTP_STRING}},
|
||||||
[OPCODE_POP] = {"POP", 1, (OperandType[]) {OPTP_INT}},
|
[OPCODE_POP] = {"POP", 1, (OperandType[]) {OPTP_INT}},
|
||||||
@@ -4,11 +4,11 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "../src/noja/utils/source.h"
|
#include "../lib/utils/source.h"
|
||||||
#include "../src/noja/compiler/parse.h"
|
#include "../lib/compiler/parse.h"
|
||||||
#include "../src/noja/compiler/compile.h"
|
#include "../lib/compiler/compile.h"
|
||||||
#include "../src/noja/common/executable.h"
|
#include "../lib/common/executable.h"
|
||||||
#include "../src/noja/assembler/assemble.h"
|
#include "../lib/assembler/assemble.h"
|
||||||
|
|
||||||
//Regular text
|
//Regular text
|
||||||
#define BLK "\e[0;30m"
|
#define BLK "\e[0;30m"
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
all: tester
|
|
||||||
|
|
||||||
libnoja.a:
|
|
||||||
make -B -C .. tests/libnoja.a BUILD_MODE=RELEASE OUTDIR=tests
|
|
||||||
|
|
||||||
tester: tester.c libnoja.a
|
|
||||||
gcc $^ -o $@ -Wall -Wextra -lm -g
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
|
|
||||||
from random import randint
|
|
||||||
|
|
||||||
NODE_KINDS = ['expr', 'if-else', 'while', 'do-while', 'return', 'comp', 'func']
|
|
||||||
EXPR_KINDS = ['int', 'float', 'string', 'array', 'object']
|
|
||||||
|
|
||||||
def generate_ident():
|
|
||||||
return 'Apple'
|
|
||||||
|
|
||||||
def generate_string_expr_node():
|
|
||||||
return 'Hello, world!'
|
|
||||||
|
|
||||||
def generate_node(kind=None, depth=0):
|
|
||||||
|
|
||||||
if kind == None and depth > 3:
|
|
||||||
return {
|
|
||||||
'kind': 'comp',
|
|
||||||
'body': []
|
|
||||||
}
|
|
||||||
|
|
||||||
if kind == None:
|
|
||||||
kind = NODE_KINDS[randint(0, len(NODE_KINDS)-1)]
|
|
||||||
|
|
||||||
assert kind in NODE_KINDS
|
|
||||||
|
|
||||||
if kind == 'expr':
|
|
||||||
|
|
||||||
return generate_expr_node(None, depth+1)
|
|
||||||
|
|
||||||
elif kind == 'if-else':
|
|
||||||
|
|
||||||
branches = randint(1, 2)
|
|
||||||
assert branches in [1, 2]
|
|
||||||
|
|
||||||
if branches == 1:
|
|
||||||
return {
|
|
||||||
'kind': 'if-else',
|
|
||||||
'cond': generate_expr_node(None, depth+1),
|
|
||||||
'if-case': generate_node(None, depth+1)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
'kind': 'if-else',
|
|
||||||
'cond': generate_expr_node(None, depth+1),
|
|
||||||
'if-case': generate_node(None, depth+1),
|
|
||||||
'else-case': generate_node(None, depth+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
elif kind == 'while':
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'while',
|
|
||||||
'cond': generate_expr_node(None, depth+1),
|
|
||||||
'body': generate_node(None, depth+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
elif kind == 'do-while':
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'do-while',
|
|
||||||
'cond': generate_expr_node(None, depth+1),
|
|
||||||
'body': generate_node(None, depth+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
elif kind == 'return':
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'return',
|
|
||||||
'value': generate_expr_node(None, depth+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
elif kind == 'comp':
|
|
||||||
|
|
||||||
n = randint(0, 3)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'comp',
|
|
||||||
'body': [generate_node(None, depth+1) for i in range(n)]
|
|
||||||
}
|
|
||||||
|
|
||||||
elif kind == 'func':
|
|
||||||
|
|
||||||
argc = randint(0, 3)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'func',
|
|
||||||
'name': generate_ident(),
|
|
||||||
'args': [generate_ident() for i in range(argc)],
|
|
||||||
'body': generate_node(None, depth+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_expr_node(expr_kind=None, depth=0):
|
|
||||||
|
|
||||||
if expr_kind == None:
|
|
||||||
expr_kind = EXPR_KINDS[randint(0, len(EXPR_KINDS)-1)]
|
|
||||||
|
|
||||||
assert expr_kind in EXPR_KINDS
|
|
||||||
|
|
||||||
if depth > 3:
|
|
||||||
expr_kind = ['int', 'float', 'string'][randint(0, 2)]
|
|
||||||
|
|
||||||
if expr_kind == 'int':
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'expr',
|
|
||||||
'expr-kind': 'int',
|
|
||||||
'value': randint(0, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
elif expr_kind == 'float':
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'expr',
|
|
||||||
'expr-kind': 'float',
|
|
||||||
'value': float(str(randint(0, 100)) + '.' + str(randint(0, 100)))
|
|
||||||
}
|
|
||||||
|
|
||||||
elif expr_kind == 'string':
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'expr',
|
|
||||||
'expr-kind': 'string',
|
|
||||||
'value': generate_string_expr_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
elif expr_kind == 'array':
|
|
||||||
|
|
||||||
n = randint(0, 3)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'expr',
|
|
||||||
'expr-kind': 'array',
|
|
||||||
'items': [generate_expr_node(None, depth+1) for i in range(n)]
|
|
||||||
}
|
|
||||||
|
|
||||||
elif expr_kind == 'object':
|
|
||||||
|
|
||||||
n = randint(0, 3)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kind': 'expr',
|
|
||||||
'expr-kind': 'object',
|
|
||||||
'items': [(generate_expr_node(None, depth+1), generate_expr_node(None, depth+1)) for i in range(n)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def serialize_expr(node):
|
|
||||||
|
|
||||||
if node['expr-kind'] == 'int':
|
|
||||||
|
|
||||||
return str(node['value'])
|
|
||||||
|
|
||||||
elif node['expr-kind'] == 'float':
|
|
||||||
|
|
||||||
return str(node['value'])
|
|
||||||
|
|
||||||
elif node['expr-kind'] == 'string':
|
|
||||||
|
|
||||||
return '"' + node['value'] + '"'
|
|
||||||
|
|
||||||
elif node['expr-kind'] == 'array':
|
|
||||||
|
|
||||||
return '[' + ', '.join([serialize_expr(item) for item in node['items']]) + ']'
|
|
||||||
|
|
||||||
elif node['expr-kind'] == 'object':
|
|
||||||
|
|
||||||
return '{' + ', '.join([serialize_expr(item[0]) + ': ' + serialize_expr(item[1]) for item in node['items']]) + '}'
|
|
||||||
|
|
||||||
def serialize(node):
|
|
||||||
|
|
||||||
if node['kind'] == 'expr':
|
|
||||||
|
|
||||||
return serialize_expr(node) + ';'
|
|
||||||
|
|
||||||
elif node['kind'] == 'if-else':
|
|
||||||
|
|
||||||
if 'else-case' in node:
|
|
||||||
return 'if ' + serialize_expr(node['cond']) + ': ' + serialize(node['if-case']) + ' else ' + serialize(node['else-case'])
|
|
||||||
else:
|
|
||||||
return 'if ' + serialize_expr(node['cond']) + ': ' + serialize(node['if-case'])
|
|
||||||
|
|
||||||
elif node['kind'] == 'while':
|
|
||||||
|
|
||||||
return 'while ' + serialize_expr(node['cond']) + ': ' + serialize(node['body'])
|
|
||||||
|
|
||||||
elif node['kind'] == 'do-while':
|
|
||||||
|
|
||||||
return 'do ' + serialize(node['body']) + ' while ' + serialize_expr(node['cond']) + ';'
|
|
||||||
|
|
||||||
elif node['kind'] == 'return':
|
|
||||||
|
|
||||||
return 'return ' + serialize_expr(node['value']) + ';'
|
|
||||||
|
|
||||||
elif node['kind'] == 'comp':
|
|
||||||
|
|
||||||
return '{' + ''.join([serialize(stmt) for stmt in node['body']]) + '}'
|
|
||||||
|
|
||||||
elif node['kind'] == 'func':
|
|
||||||
|
|
||||||
return 'fun ' + node['name'] + '(' + ', '.join(node['args']) + ') ' + serialize(node['body'])
|
|
||||||
|
|
||||||
print(serialize(generate_node()))
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
return none;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHNNE;
|
||||||
|
RETURN 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
-1;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
NEG;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
+1;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
POS;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
not 1;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
NOT;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 == 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
EQL;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 != 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
NQL;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 < 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
LSS;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 > 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
GRT;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 <= 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
LEQ;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 >= 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
GEQ;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 and 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
AND;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 or 2;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
OR;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 + 2 * 3;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
PUSHINT 3;
|
||||||
|
MUL;
|
||||||
|
ADD;
|
||||||
|
|
||||||
|
POP 1;
|
||||||
|
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 * 2 + 3;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
MUL;
|
||||||
|
PUSHINT 3;
|
||||||
|
ADD;
|
||||||
|
|
||||||
|
POP 1;
|
||||||
|
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
(1 + 2) * 3;
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
ADD;
|
||||||
|
PUSHINT 3;
|
||||||
|
MUL;
|
||||||
|
|
||||||
|
POP 1;
|
||||||
|
|
||||||
|
RETURN 0;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
#source
|
||||||
|
|
||||||
|
1 + (2 * 3);
|
||||||
|
|
||||||
|
#bytecode
|
||||||
|
|
||||||
|
PUSHINT 1;
|
||||||
|
PUSHINT 2;
|
||||||
|
PUSHINT 3;
|
||||||
|
MUL;
|
||||||
|
ADD;
|
||||||
|
POP 1;
|
||||||
|
RETURN 0;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user