✨ num literals.
parent
e434d5c4ae
commit
46dc8b8c2d
9
Makefile
9
Makefile
|
@ -7,13 +7,16 @@ build:
|
|||
tests: build
|
||||
build/roza-tests
|
||||
|
||||
install: build
|
||||
install: tests
|
||||
meson install -C build
|
||||
|
||||
check:
|
||||
@cppcheck --language=c --enable=all lib src -q \
|
||||
--suppress=missingIncludeSystem
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppress=missingInclude \
|
||||
--suppress=unmatchedSuppression
|
||||
|
||||
@cppcheck --language=c --enable=all tests/units -q \
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppress=unusedFunction
|
||||
--suppress=unusedFunction \
|
||||
--suppress=unmatchedSuppression
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
MOD ::= EXPR*
|
||||
EXPR ::= num
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef RZ_COMMONS_H
|
||||
#define RZ_COMMONS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <conf.h>
|
||||
#include "str.h"
|
||||
|
||||
#define RZ_STR_LIMIT 256
|
||||
#define RZ_STACK_LIMIT 1024
|
||||
#define RZ_MAX_TYPES 256
|
||||
|
||||
#define RZ_NO_PARAM (-1)
|
||||
typedef int param_t;
|
||||
|
||||
#define RZ_ENUM_ID(X) X
|
||||
#define RZ_ENUM_STR(X) #X
|
||||
|
||||
#define RZ_ENUM_H(PREFIX, NAME) \
|
||||
typedef enum PREFIX { NAME(RZ_ENUM_ID) } PREFIX; \
|
||||
extern char const* PREFIX ## Str []
|
||||
|
||||
#define RZ_ENUM_C(PREFIX, NAME) \
|
||||
char const* PREFIX ## Str [] = { NAME(RZ_ENUM_STR) }
|
||||
|
||||
#endif
|
|
@ -0,0 +1,50 @@
|
|||
#include "compiler.h"
|
||||
#include "lib/commons.h"
|
||||
|
||||
void compiler_init(compiler_t* compiler, mod_t* mod, tysy_t* tysy, err_t* err)
|
||||
{
|
||||
assert(compiler);
|
||||
assert(err);
|
||||
|
||||
compiler->mod = mod;
|
||||
compiler->tysy = tysy;
|
||||
compiler->err = err;
|
||||
}
|
||||
|
||||
void compiler_free(compiler_t* compiler)
|
||||
{
|
||||
assert(compiler);
|
||||
}
|
||||
|
||||
void compiler_run(compiler_t* compiler, node_t* node)
|
||||
{
|
||||
assert(compiler);
|
||||
assert(node);
|
||||
|
||||
switch (node->type)
|
||||
{
|
||||
case NODE_MOD: {
|
||||
for (size_t i=0; i<node->children.size; i++)
|
||||
{
|
||||
compiler_run(compiler, (node_t*) node->children.data[i]);
|
||||
}
|
||||
} break;
|
||||
|
||||
case NODE_NUM: {
|
||||
double value = atof(node->value.data);
|
||||
value_t* val = tysy_new_num(compiler->tysy, value);
|
||||
Opcode op = OP_PUSH;
|
||||
param_t param = (param_t) mod_push_new_value(compiler->mod, val);
|
||||
|
||||
mod_push_instr(compiler->mod, op, param);
|
||||
|
||||
} break;
|
||||
|
||||
default: {
|
||||
char msg[RZ_STR_LIMIT];
|
||||
snprintf(msg, RZ_STR_LIMIT, "Unknown node '%s'",
|
||||
NodeTypeStr[node->type]);
|
||||
err_error(compiler->err, msg, node->line);
|
||||
} break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef RZ_COMPILER_H
|
||||
#define RZ_COMPILER_H
|
||||
|
||||
#include "commons.h"
|
||||
#include "err.h"
|
||||
#include "mod.h"
|
||||
#include "node.h"
|
||||
#include "tysy.h"
|
||||
|
||||
typedef struct {
|
||||
mod_t* mod;
|
||||
tysy_t* tysy;
|
||||
err_t* err;
|
||||
} compiler_t;
|
||||
|
||||
void compiler_init(compiler_t* compiler, mod_t* mod, tysy_t* tysy, err_t* err);
|
||||
void compiler_free(compiler_t* compiler);
|
||||
|
||||
void compiler_run(compiler_t* compiler, node_t* node);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
#include "err.h"
|
||||
|
||||
void err_init(err_t* err)
|
||||
{
|
||||
assert(err);
|
||||
err->size = 0;
|
||||
}
|
||||
|
||||
void err_free(err_t* err)
|
||||
{
|
||||
assert(err);
|
||||
|
||||
for (size_t i=0; i<err->size; i++)
|
||||
{
|
||||
free(err->errors[i]->what);
|
||||
free(err->errors[i]);
|
||||
}
|
||||
|
||||
err->size = 0;
|
||||
}
|
||||
|
||||
void err_error(err_t* err, char* what, int line)
|
||||
{
|
||||
assert(err);
|
||||
assert(err->size + 1 < RZ_ERROR_STACK_SIZE);
|
||||
|
||||
err->errors[err->size] = malloc(sizeof(err_msg_t));
|
||||
err->errors[err->size]->what = strdup(what);
|
||||
err->errors[err->size]->line = line;
|
||||
err->size++;
|
||||
}
|
||||
|
||||
void err_abort(err_t* err)
|
||||
{
|
||||
assert(err);
|
||||
|
||||
for (size_t i=0; i<err->size; i++)
|
||||
{
|
||||
fprintf(stderr, "ERR(%d) %s\n",
|
||||
err->errors[i]->line,
|
||||
err->errors[i]->what);
|
||||
}
|
||||
|
||||
err_free(err);
|
||||
|
||||
abort();
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef RZ_ERR_H
|
||||
#define RZ_ERR_H
|
||||
|
||||
#define RZ_ERROR_STACK_SIZE 256
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
typedef struct {
|
||||
char* what;
|
||||
int line;
|
||||
} err_msg_t;
|
||||
|
||||
typedef struct {
|
||||
size_t size;
|
||||
err_msg_t* errors[RZ_ERROR_STACK_SIZE];
|
||||
} err_t;
|
||||
|
||||
void err_init(err_t* err);
|
||||
void err_free(err_t* err);
|
||||
|
||||
void err_error(err_t* err, char* what, int line);
|
||||
void err_abort(err_t* err);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,95 @@
|
|||
#include "lexer.h"
|
||||
#include "lib/commons.h"
|
||||
|
||||
void lexer_init(lexer_t* lexer, char const* source, err_t* err)
|
||||
{
|
||||
assert(lexer);
|
||||
lexer->source = strdup(source);
|
||||
lexer->cursor = 0;
|
||||
lexer->err = err;
|
||||
lexer->line = 1;
|
||||
}
|
||||
|
||||
void lexer_free(lexer_t* lexer)
|
||||
{
|
||||
assert(lexer);
|
||||
free(lexer->source);
|
||||
lexer->source = NULL;
|
||||
}
|
||||
|
||||
node_t* lexer_try_new_next(lexer_t* lexer)
|
||||
{
|
||||
assert(lexer);
|
||||
size_t len = strlen(lexer->source);
|
||||
|
||||
// skip spaces
|
||||
{
|
||||
while (lexer->cursor < len
|
||||
&& isspace(lexer->source[lexer->cursor]))
|
||||
{
|
||||
if (lexer->source[lexer->cursor] == '\n')
|
||||
{
|
||||
lexer->line++;
|
||||
}
|
||||
|
||||
lexer->cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
// scan num
|
||||
{
|
||||
size_t cursor = lexer->cursor;
|
||||
|
||||
str_t res_str;
|
||||
str_init(&res_str);
|
||||
|
||||
if (cursor < len && lexer->source[cursor] == '-')
|
||||
{
|
||||
str_push(&res_str, lexer->source[cursor]);
|
||||
cursor++;
|
||||
}
|
||||
|
||||
while (cursor < len
|
||||
&& isdigit(lexer->source[cursor]))
|
||||
{
|
||||
str_push(&res_str, lexer->source[cursor]);
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
if (cursor < len && lexer->source[cursor] == '.')
|
||||
{
|
||||
str_push(&res_str, lexer->source[cursor]);
|
||||
cursor++;
|
||||
|
||||
while (cursor < len
|
||||
&& isdigit(lexer->source[cursor]))
|
||||
{
|
||||
str_push(&res_str, lexer->source[cursor]);
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (res_str.size > 0
|
||||
&& (cursor >= len || isspace(lexer->source[cursor])))
|
||||
{
|
||||
node_t* tok = malloc(sizeof(node_t));
|
||||
node_init(tok, NODE_NUM, res_str.data, lexer->line);
|
||||
str_free(&res_str);
|
||||
|
||||
lexer->cursor = cursor;
|
||||
return tok;
|
||||
}
|
||||
|
||||
str_free(&res_str);
|
||||
}
|
||||
|
||||
if (lexer->cursor < len && lexer->err)
|
||||
{
|
||||
size_t const SZ = RZ_STR_LIMIT;
|
||||
char msg[SZ];
|
||||
snprintf(msg, SZ, "unexpected symbol '%c'", lexer->source[lexer->cursor]);
|
||||
err_error(lexer->err, msg, lexer->line);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef RZ_LEXER_H
|
||||
#define RZ_LEXER_H
|
||||
|
||||
#include "commons.h"
|
||||
#include "node.h"
|
||||
#include "err.h"
|
||||
|
||||
typedef struct {
|
||||
err_t* err;
|
||||
char* source;
|
||||
size_t cursor;
|
||||
int line;
|
||||
} lexer_t;
|
||||
|
||||
void lexer_init(lexer_t* lexer, char const* source, err_t* err);
|
||||
void lexer_free(lexer_t* lexer);
|
||||
|
||||
node_t* lexer_try_new_next(lexer_t* lexer);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,124 @@
|
|||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
#include "loader.h"
|
||||
#include "compiler.h"
|
||||
#include "mod.h"
|
||||
#include "vm.h"
|
||||
|
||||
void loader_init(loader_t* loader)
|
||||
{
|
||||
assert(loader);
|
||||
}
|
||||
|
||||
void loader_free(loader_t* loader)
|
||||
{
|
||||
assert(loader);
|
||||
}
|
||||
|
||||
void loader_ldfile(loader_t* loader, char const* path, int debug)
|
||||
{
|
||||
assert(loader);
|
||||
str_t source;
|
||||
str_init(&source);
|
||||
|
||||
// read sources
|
||||
{
|
||||
FILE* file = fopen(path, "r");
|
||||
|
||||
if (!file)
|
||||
{
|
||||
fprintf(stderr, "Cannot open file '%s'.\n", path);
|
||||
abort();
|
||||
}
|
||||
|
||||
char buf;
|
||||
size_t sz;
|
||||
|
||||
while ( (sz = fread(&buf, sizeof(char), 1, file)) )
|
||||
{
|
||||
str_push(&source, buf);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
if (str_empty(&source))
|
||||
{
|
||||
str_free(&source);
|
||||
return;
|
||||
}
|
||||
|
||||
tysy_t tysy;
|
||||
tysy_init(&tysy);
|
||||
|
||||
err_t err;
|
||||
err_init(&err);
|
||||
|
||||
lexer_t lex;
|
||||
lexer_init(&lex, source.data, &err);
|
||||
|
||||
parser_t parser;
|
||||
parser_init(&parser, &lex, &err);
|
||||
|
||||
node_t* node = parser_try_new_tree(&parser);
|
||||
|
||||
if (node)
|
||||
{
|
||||
mod_t mod;
|
||||
mod_init(&mod);
|
||||
|
||||
// compile
|
||||
{
|
||||
compiler_t compiler;
|
||||
compiler_init(&compiler, &mod, &tysy, &err);
|
||||
compiler_run(&compiler, node);
|
||||
compiler_free(&compiler);
|
||||
node_free(node);
|
||||
free(node);
|
||||
}
|
||||
|
||||
// execute
|
||||
{
|
||||
vm_t vm;
|
||||
vm_init(&vm);
|
||||
|
||||
vm_exec_mod(&vm, &mod);
|
||||
|
||||
if (debug)
|
||||
{
|
||||
{
|
||||
char msg[RZ_STR_LIMIT];
|
||||
mod_str(&mod, msg, RZ_STR_LIMIT);
|
||||
printf("%s", msg);
|
||||
}
|
||||
|
||||
{
|
||||
char msg[RZ_STR_LIMIT];
|
||||
vm_stack_str(&vm, msg, RZ_STR_LIMIT);
|
||||
printf("\n======== STACK ========\n");
|
||||
printf("%s\n", msg);
|
||||
}
|
||||
}
|
||||
|
||||
vm_free(&vm);
|
||||
}
|
||||
|
||||
mod_free(&mod);
|
||||
}
|
||||
|
||||
// free
|
||||
parser_free(&parser);
|
||||
lexer_free(&lex);
|
||||
str_free(&source);
|
||||
|
||||
if (err.size > 0)
|
||||
{
|
||||
err_abort(&err);
|
||||
}
|
||||
else
|
||||
{
|
||||
err_free(&err);
|
||||
}
|
||||
|
||||
tysy_free(&tysy);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef RZ_LOADER_H
|
||||
#define RZ_LOADER_H
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
} loader_t;
|
||||
|
||||
void loader_init(loader_t* loader);
|
||||
void loader_free(loader_t* loader);
|
||||
|
||||
void loader_ldfile(loader_t* loader, char const* path, int debug);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,118 @@
|
|||
#include "mod.h"
|
||||
|
||||
void mod_init(mod_t* mod)
|
||||
{
|
||||
assert(mod);
|
||||
|
||||
mod->program.size = 0;
|
||||
mod->program.cap = 0;
|
||||
mod->program.ops = NULL;
|
||||
mod->program.params = NULL;
|
||||
|
||||
mod->values.size = 0;
|
||||
mod->values.cap = 0;
|
||||
mod->values.data = NULL;
|
||||
}
|
||||
|
||||
void mod_free(mod_t* mod)
|
||||
{
|
||||
assert(mod);
|
||||
free(mod->program.ops);
|
||||
free(mod->program.params);
|
||||
mod->program.size = 0;
|
||||
mod->program.cap = 0;
|
||||
|
||||
for (size_t i=0; i<mod->values.size; i++)
|
||||
{
|
||||
value_free(mod->values.data[i]);
|
||||
free(mod->values.data[i]);
|
||||
}
|
||||
|
||||
free(mod->values.data);
|
||||
mod->values.data = NULL;
|
||||
mod->values.size = 0;
|
||||
mod->values.cap = 0;
|
||||
}
|
||||
|
||||
void mod_push_instr(mod_t* mod, Opcode op, param_t param)
|
||||
{
|
||||
assert(mod);
|
||||
|
||||
if (mod->program.cap == 0)
|
||||
{
|
||||
mod->program.cap = 2;
|
||||
mod->program.ops = malloc(sizeof(Opcode) * mod->program.cap);
|
||||
mod->program.params = malloc(sizeof(param_t) * mod->program.cap);
|
||||
}
|
||||
|
||||
if (mod->program.size >= mod->program.cap)
|
||||
{
|
||||
mod->program.cap *= 2;
|
||||
mod->program.ops = realloc(mod->program.ops,
|
||||
sizeof(Opcode) * mod->program.cap);
|
||||
mod->program.params = realloc(mod->program.params,
|
||||
sizeof(param_t) * mod->program.cap);
|
||||
}
|
||||
|
||||
mod->program.ops[mod->program.size] = op;
|
||||
mod->program.params[mod->program.size] = param;
|
||||
mod->program.size++;
|
||||
}
|
||||
|
||||
size_t mod_str(mod_t* mod, char* buffer, size_t size)
|
||||
{
|
||||
size_t sz = 0;
|
||||
|
||||
sz += snprintf(buffer + sz, size - sz, "======== VALUES ========\n");
|
||||
for (size_t i=0; i<mod->values.size; i++)
|
||||
{
|
||||
sz += snprintf(buffer + sz, size - sz, "%d| ", (int) i);
|
||||
sz += value_str(mod->values.data[i], buffer + sz, size - sz);
|
||||
sz += snprintf(buffer + sz, size - sz, "\n");
|
||||
}
|
||||
|
||||
sz += snprintf(buffer + sz, size - sz, "\n======== PROGRAM ========\n");
|
||||
for (size_t i=0; i<mod->program.size; i++)
|
||||
{
|
||||
sz += snprintf(buffer + sz, size - sz, "%d| %s %d\n",
|
||||
(int) i,
|
||||
OpcodeStr[mod->program.ops[i]] + strlen("OP_"),
|
||||
mod->program.params[i]);
|
||||
}
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
size_t mod_push_new_value(mod_t* mod, value_t* value)
|
||||
{
|
||||
assert(mod);
|
||||
assert(value);
|
||||
|
||||
for (size_t i=0; i<mod->values.size; i++)
|
||||
{
|
||||
if (value_eq(value, mod->values.data[i]))
|
||||
{
|
||||
value_free(value);
|
||||
free(value);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
if (mod->values.size == 0)
|
||||
{
|
||||
mod->values.cap = 2;
|
||||
mod->values.data = malloc(sizeof(value_t*) * mod->values.cap);
|
||||
}
|
||||
|
||||
if (mod->values.size >= mod->values.cap)
|
||||
{
|
||||
mod->values.cap *= 2;
|
||||
mod->values.data = realloc(mod->values.data,
|
||||
sizeof(value_t*) * mod->values.cap);
|
||||
}
|
||||
|
||||
mod->values.data[mod->values.size] = value;
|
||||
mod->values.size++;
|
||||
|
||||
return mod->values.size - 1;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef RZ_MOD_H
|
||||
#define RZ_MOD_H
|
||||
|
||||
#include "commons.h"
|
||||
#include "opcodes.h"
|
||||
#include "value.h"
|
||||
|
||||
typedef struct {
|
||||
struct {
|
||||
size_t size;
|
||||
size_t cap;
|
||||
Opcode* ops;
|
||||
param_t* params;
|
||||
} program;
|
||||
|
||||
struct {
|
||||
size_t size;
|
||||
size_t cap;
|
||||
value_t** data;
|
||||
} values;
|
||||
} mod_t;
|
||||
|
||||
void mod_init(mod_t* mod);
|
||||
void mod_free(mod_t* mod);
|
||||
|
||||
void mod_push_instr(mod_t* mod, Opcode op, param_t param);
|
||||
size_t mod_str(mod_t* mod, char* buffer, size_t size);
|
||||
|
||||
size_t mod_push_new_value(mod_t* mod, value_t* value);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,91 @@
|
|||
#include "node.h"
|
||||
|
||||
RZ_ENUM_C(NodeType, NODE_TYPE);
|
||||
|
||||
void node_init(node_t* node, NodeType type, char* value, int line)
|
||||
{
|
||||
assert(node);
|
||||
assert(value);
|
||||
|
||||
node->type = type;
|
||||
str_init(&node->value);
|
||||
str_append(&node->value, value);
|
||||
|
||||
node->line = line;
|
||||
|
||||
node->children.size = 0;
|
||||
node->children.cap = 0;
|
||||
node->children.data = NULL;
|
||||
}
|
||||
|
||||
void node_free(node_t* node)
|
||||
{
|
||||
assert(node);
|
||||
str_free(&node->value);
|
||||
|
||||
for (size_t i=0; i<node->children.size; i++)
|
||||
{
|
||||
node_free((node_t*) node->children.data[i]);
|
||||
free(node->children.data[i]);
|
||||
}
|
||||
|
||||
free(node->children.data);
|
||||
node->children.size = 0;
|
||||
node->children.cap = 0;
|
||||
}
|
||||
|
||||
void node_add_child(node_t* node, node_t* child)
|
||||
{
|
||||
assert(node);
|
||||
assert(child);
|
||||
|
||||
if (node->children.cap == 0)
|
||||
{
|
||||
node->children.cap = 2;
|
||||
node->children.data = malloc(sizeof(node_t) * node->children.cap);
|
||||
}
|
||||
|
||||
if (node->children.size >= node->children.cap)
|
||||
{
|
||||
node->children.cap *= 2;
|
||||
node->children.data = realloc(node->children.data,
|
||||
sizeof(node_t) * node->children.cap);
|
||||
}
|
||||
|
||||
size_t sz = node->children.size;
|
||||
node->children.data[sz] = (struct node_t*) child;
|
||||
node->children.size++;
|
||||
}
|
||||
|
||||
size_t node_str(node_t* node, char* buffer, size_t size)
|
||||
{
|
||||
assert(node);
|
||||
size_t sz = 0;
|
||||
|
||||
sz += snprintf(buffer + sz, size - sz, "%s",
|
||||
NodeTypeStr[node->type] + strlen("NODE_"));
|
||||
|
||||
if (!str_empty(&node->value))
|
||||
{
|
||||
sz += snprintf(buffer + sz, size - sz, "[%s]", node->value.data);
|
||||
}
|
||||
|
||||
if (node->children.size > 0)
|
||||
{
|
||||
sz += snprintf(buffer +sz, size - sz, "(");
|
||||
|
||||
for (size_t i=0; i<node->children.size; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
sz += snprintf(buffer +sz, size - sz, ",");
|
||||
}
|
||||
|
||||
sz += node_str((node_t*) node->children.data[i], buffer + sz, size - sz);
|
||||
}
|
||||
|
||||
sz += snprintf(buffer +sz, size - sz, ")");
|
||||
}
|
||||
|
||||
return sz;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef RZ_NODE_H
|
||||
#define RZ_NODE_H
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
#define NODE_TYPE(G) \
|
||||
G(NODE_MOD), \
|
||||
G(NODE_NUM)
|
||||
|
||||
RZ_ENUM_H(NodeType, NODE_TYPE);
|
||||
|
||||
typedef struct {
|
||||
NodeType type;
|
||||
str_t value;
|
||||
int line;
|
||||
|
||||
struct {
|
||||
struct node_t** data;
|
||||
size_t size;
|
||||
size_t cap;
|
||||
} children;
|
||||
} node_t;
|
||||
|
||||
void node_init(node_t* node, NodeType type, char* value, int line);
|
||||
void node_free(node_t* node);
|
||||
|
||||
void node_add_child(node_t* node, node_t* child);
|
||||
|
||||
size_t node_str(node_t* node, char* buffer, size_t size);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,3 @@
|
|||
#include "opcodes.h"
|
||||
|
||||
RZ_ENUM_C(Opcode, OPCODES);
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef RZ_OPCODES_H
|
||||
#define RZ_OPCODES_H
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
#define OPCODES(G) \
|
||||
G(OP_HALT), \
|
||||
G(OP_PUSH), G(OP_POP)
|
||||
|
||||
RZ_ENUM_H(Opcode, OPCODES);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
#include "parser.h"
|
||||
#include "lib/lexer.h"
|
||||
#include "lib/node.h"
|
||||
|
||||
void parser_init(parser_t* parser, lexer_t* lexer, err_t* err)
|
||||
{
|
||||
assert(parser);
|
||||
parser->lexer = lexer;
|
||||
parser->err = err;
|
||||
}
|
||||
|
||||
void parser_free(parser_t* parser)
|
||||
{
|
||||
assert(parser);
|
||||
}
|
||||
|
||||
node_t* parser_try_new_tree(parser_t* parser)
|
||||
{
|
||||
assert(parser);
|
||||
return parser_try_new_mod(parser);
|
||||
}
|
||||
|
||||
node_t* parser_try_new_mod(parser_t* parser)
|
||||
{
|
||||
assert(parser);
|
||||
node_t* mod = malloc(sizeof(node_t));
|
||||
node_init(mod, NODE_MOD, "", parser->lexer->line);
|
||||
size_t len = strlen(parser->lexer->source);
|
||||
|
||||
while (parser->lexer->cursor < len)
|
||||
{
|
||||
node_t* expr = parser_try_new_expr(parser);
|
||||
|
||||
if (expr)
|
||||
{
|
||||
node_add_child(mod, expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
node_free(mod);
|
||||
free(mod);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
node_t* parser_try_new_expr(parser_t* parser)
|
||||
{
|
||||
assert(parser);
|
||||
return parser_try_new_num(parser);
|
||||
}
|
||||
|
||||
node_t* parser_try_new_num(parser_t* parser)
|
||||
{
|
||||
assert(parser);
|
||||
return parser_try_new_consume(parser, NODE_NUM);
|
||||
}
|
||||
|
||||
node_t* parser_try_new_consume(parser_t* parser, NodeType type)
|
||||
{
|
||||
assert(parser);
|
||||
node_t* next = lexer_try_new_next(parser->lexer);
|
||||
|
||||
if (!next)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (next->type != type)
|
||||
{
|
||||
size_t const SZ = RZ_STR_LIMIT;
|
||||
char err_msg[SZ];
|
||||
snprintf(err_msg, SZ, "unexpected node '%s'", NodeTypeStr[next->type]);
|
||||
err_error(parser->err, err_msg, next->line);
|
||||
node_free(next);
|
||||
free(next);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef RZ_PARSER_H
|
||||
#define RZ_PARSER_H
|
||||
|
||||
#include "commons.h"
|
||||
#include "err.h"
|
||||
#include "lexer.h"
|
||||
|
||||
typedef struct {
|
||||
lexer_t* lexer;
|
||||
err_t* err;
|
||||
} parser_t;
|
||||
|
||||
void parser_init(parser_t* parser, lexer_t* lexer, err_t* err);
|
||||
void parser_free(parser_t* parser);
|
||||
|
||||
node_t* parser_try_new_tree(parser_t* parser);
|
||||
node_t* parser_try_new_mod(parser_t* parser);
|
||||
node_t* parser_try_new_expr(parser_t* parser);
|
||||
node_t* parser_try_new_num(parser_t* parser);
|
||||
|
||||
node_t* parser_try_new_consume(parser_t* parser, NodeType type);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,67 @@
|
|||
#include "str.h"
|
||||
|
||||
void str_init(str_t* str)
|
||||
{
|
||||
assert(str);
|
||||
str->data = NULL;
|
||||
str->size = 0;
|
||||
str->cap = 0;
|
||||
}
|
||||
|
||||
void str_free(str_t* str)
|
||||
{
|
||||
assert(str);
|
||||
|
||||
free(str->data);
|
||||
str->data = NULL;
|
||||
str->size = 0;
|
||||
str->cap = 0;
|
||||
}
|
||||
|
||||
void str_resize(str_t* str)
|
||||
{
|
||||
assert(str);
|
||||
|
||||
if (str->data == NULL)
|
||||
{
|
||||
str->cap = 2;
|
||||
str->data = calloc(str->cap + 1, sizeof(char));
|
||||
}
|
||||
|
||||
if (str->size >= str->cap)
|
||||
{
|
||||
str->cap *= 2;
|
||||
str->data = realloc(str->data, sizeof(char) * (str->cap + 1));
|
||||
}
|
||||
}
|
||||
|
||||
void str_push(str_t* str, char c)
|
||||
{
|
||||
assert(str);
|
||||
|
||||
str_resize(str);
|
||||
str->data[str->size] = c;
|
||||
|
||||
str_resize(str);
|
||||
str->data[str->size + 1] = '\0';
|
||||
str->size++;
|
||||
}
|
||||
|
||||
void str_append(str_t* str, char const* src)
|
||||
{
|
||||
assert(str);
|
||||
assert(src);
|
||||
|
||||
size_t len = strlen(src);
|
||||
|
||||
for (size_t i=0; i<len; i++)
|
||||
{
|
||||
str_push(str, src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int str_empty(str_t* str)
|
||||
{
|
||||
assert(str);
|
||||
return str->size == 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef RZ_STR_H
|
||||
#define RZ_STR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct {
|
||||
char* data;
|
||||
size_t size;
|
||||
size_t cap;
|
||||
} str_t;
|
||||
|
||||
void str_init(str_t* str);
|
||||
void str_free(str_t* str);
|
||||
|
||||
void str_resize(str_t* str);
|
||||
void str_push(str_t* str, char c);
|
||||
void str_append(str_t* str, char const* src);
|
||||
int str_empty(str_t* str);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,22 @@
|
|||
#include "type.h"
|
||||
|
||||
RZ_ENUM_C(TypeKind, TYPE_KIND);
|
||||
|
||||
void type_init(type_t* type, TypeKind kind)
|
||||
{
|
||||
assert(type);
|
||||
type->kind = kind;
|
||||
}
|
||||
|
||||
void type_free(type_t* type)
|
||||
{
|
||||
assert(type);
|
||||
}
|
||||
|
||||
int type_eq(type_t* type, type_t* rhs)
|
||||
{
|
||||
assert(type);
|
||||
assert(rhs);
|
||||
|
||||
return type->kind == rhs->kind;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef RZ_TYPE_H
|
||||
#define RZ_TYPE_H
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
#define TYPE_KIND(G) \
|
||||
G(TYPE_NUM)
|
||||
|
||||
RZ_ENUM_H(TypeKind, TYPE_KIND);
|
||||
|
||||
typedef struct {
|
||||
TypeKind kind;
|
||||
} type_t;
|
||||
|
||||
void type_init(type_t* type, TypeKind kind);
|
||||
void type_free(type_t* type);
|
||||
|
||||
int type_eq(type_t* type, type_t* rhs);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,67 @@
|
|||
#include "tysy.h"
|
||||
|
||||
void tysy_init(tysy_t* tysy)
|
||||
{
|
||||
assert(tysy);
|
||||
tysy->size = 0;
|
||||
|
||||
// num
|
||||
{
|
||||
type_t* num = malloc(sizeof(type_t));
|
||||
type_init(num, TYPE_NUM);
|
||||
tysy_register_new_type(tysy, "num", num);
|
||||
}
|
||||
}
|
||||
|
||||
void tysy_free(tysy_t* tysy)
|
||||
{
|
||||
assert(tysy);
|
||||
|
||||
for (size_t i=0; i<tysy->size; i++)
|
||||
{
|
||||
type_free(tysy->types[i]);
|
||||
free(tysy->types[i]);
|
||||
free(tysy->names[i]);
|
||||
}
|
||||
|
||||
tysy->size = 0;
|
||||
}
|
||||
|
||||
void tysy_register_new_type(tysy_t* tysy, char* name, type_t* type)
|
||||
{
|
||||
assert(tysy);
|
||||
assert(name);
|
||||
assert(type);
|
||||
assert(tysy->size + 1 < RZ_MAX_TYPES);
|
||||
|
||||
tysy->types[tysy->size] = type;
|
||||
tysy->names[tysy->size] = strdup(name);
|
||||
tysy->size++;
|
||||
}
|
||||
|
||||
type_t* tysy_try_find_type(tysy_t* tysy, char* name)
|
||||
{
|
||||
assert(tysy);
|
||||
assert(name);
|
||||
|
||||
for (size_t i=0; i<tysy->size; i++)
|
||||
{
|
||||
if (strcmp(name, tysy->names[i]) == 0)
|
||||
{
|
||||
return tysy->types[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
value_t* tysy_new_num(tysy_t* tysy, double value)
|
||||
{
|
||||
assert(tysy);
|
||||
|
||||
value_t* val = malloc(sizeof(value_t));
|
||||
value_init(val, tysy_try_find_type(tysy, "num"));
|
||||
val->value.num = value;
|
||||
|
||||
return val;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef RZ_TYSY_H
|
||||
#define RZ_TYSY_H
|
||||
|
||||
#include "commons.h"
|
||||
#include "value.h"
|
||||
|
||||
typedef struct {
|
||||
type_t* types[RZ_MAX_TYPES];
|
||||
char* names[RZ_MAX_TYPES];
|
||||
size_t size;
|
||||
} tysy_t;
|
||||
|
||||
void tysy_init(tysy_t* tysy);
|
||||
void tysy_free(tysy_t* tysy);
|
||||
|
||||
void tysy_register_new_type(tysy_t* tysy, char* name, type_t* type);
|
||||
type_t* tysy_try_find_type(tysy_t* tysy, char* name);
|
||||
|
||||
value_t* tysy_new_num(tysy_t* tysy, double value);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,52 @@
|
|||
#include "value.h"
|
||||
|
||||
void value_init(value_t* value, type_t* type)
|
||||
{
|
||||
assert(value);
|
||||
assert(type);
|
||||
|
||||
value->type = type;
|
||||
}
|
||||
|
||||
void value_free(value_t* value)
|
||||
{
|
||||
assert(value);
|
||||
}
|
||||
|
||||
int value_eq(value_t* value, value_t* rhs)
|
||||
{
|
||||
if (!type_eq(value->type, rhs->type))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (value->type->kind)
|
||||
{
|
||||
case TYPE_NUM: {
|
||||
return value->value.num == rhs->value.num;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
fprintf(stderr, "Cannot compare value of type '%s'.\n",
|
||||
TypeKindStr[value->type->kind]);
|
||||
abort();
|
||||
};
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t value_str(value_t* value, char* buffer, size_t size)
|
||||
{
|
||||
assert(value);
|
||||
assert(buffer);
|
||||
|
||||
switch (value->type->kind)
|
||||
{
|
||||
case TYPE_NUM:
|
||||
return snprintf(buffer, size, "%lf", value->value.num);
|
||||
break;
|
||||
|
||||
default: return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef RZ_VALUE_H
|
||||
#define RZ_VALUE_H
|
||||
|
||||
#include "type.h"
|
||||
|
||||
typedef struct {
|
||||
type_t* type;
|
||||
|
||||
union {
|
||||
double num;
|
||||
} value;
|
||||
|
||||
} value_t;
|
||||
|
||||
void value_init(value_t* value, type_t* type);
|
||||
void value_free(value_t* value);
|
||||
|
||||
int value_eq(value_t* value, value_t* rhs);
|
||||
|
||||
size_t value_str(value_t* value, char* buffer, size_t size);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,70 @@
|
|||
#include "vm.h"
|
||||
#include "lib/commons.h"
|
||||
|
||||
void vm_init(vm_t* vm)
|
||||
{
|
||||
assert(vm);
|
||||
|
||||
vm->pc = 0;
|
||||
vm->bp = 0;
|
||||
vm->sp = 0;
|
||||
}
|
||||
|
||||
void vm_free(vm_t* vm)
|
||||
{
|
||||
assert(vm);
|
||||
}
|
||||
|
||||
size_t vm_stack_str(vm_t* vm, char* buffer, size_t size)
|
||||
{
|
||||
assert(vm);
|
||||
assert(buffer);
|
||||
|
||||
size_t sz = 0;
|
||||
|
||||
for (size_t i=0; i<vm->sp; i++)
|
||||
{
|
||||
sz += snprintf(buffer + sz, size - sz, "| %d |\n", vm->stack[vm->sp - 1 - i]);
|
||||
}
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
void vm_exec_mod(vm_t* vm, mod_t* mod)
|
||||
{
|
||||
assert(vm);
|
||||
vm->pc = 0;
|
||||
|
||||
while (vm->pc < mod->program.size)
|
||||
{
|
||||
vm_exec_instr(vm, mod->program.ops[vm->pc],
|
||||
mod->program.params[vm->pc]);
|
||||
}
|
||||
}
|
||||
|
||||
void vm_exec_instr(vm_t* vm, Opcode op, param_t param)
|
||||
{
|
||||
assert(vm);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
|
||||
case OP_PUSH: {
|
||||
if (vm->sp + 1 >= RZ_STACK_LIMIT)
|
||||
{
|
||||
fprintf(stderr, "Stack overflow\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
vm->stack[vm->sp] = param;
|
||||
vm->sp++;
|
||||
vm->pc++;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
fprintf(stderr, "Cannot execute unknown opcode '%s'.\n",
|
||||
OpcodeStr[op]);
|
||||
abort();
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef RZ_VM_H
|
||||
#define RZ_VM_H
|
||||
|
||||
#include "commons.h"
|
||||
#include "opcodes.h"
|
||||
#include "mod.h"
|
||||
|
||||
typedef struct {
|
||||
param_t stack[RZ_STACK_LIMIT];
|
||||
size_t pc;
|
||||
size_t bp;
|
||||
size_t sp;
|
||||
} vm_t;
|
||||
|
||||
void vm_init(vm_t* vm);
|
||||
void vm_free(vm_t* vm);
|
||||
|
||||
size_t vm_stack_str(vm_t* vm, char* buffer, size_t size);
|
||||
|
||||
void vm_exec_mod(vm_t* vm, mod_t* mod);
|
||||
void vm_exec_instr(vm_t* vm, Opcode op, param_t param);
|
||||
|
||||
#endif
|
25
meson.build
25
meson.build
|
@ -16,6 +16,28 @@ configure_file(
|
|||
roza_lib = static_library(
|
||||
'roza',
|
||||
sources: [
|
||||
# utils
|
||||
'lib/str.c',
|
||||
|
||||
# core
|
||||
'lib/err.c',
|
||||
'lib/loader.c',
|
||||
|
||||
# lang
|
||||
'lib/node.c',
|
||||
'lib/lexer.c',
|
||||
'lib/parser.c',
|
||||
'lib/type.c',
|
||||
'lib/value.c',
|
||||
'lib/tysy.c',
|
||||
|
||||
# comp
|
||||
'lib/opcodes.c',
|
||||
'lib/mod.c',
|
||||
'lib/compiler.c',
|
||||
|
||||
# exec
|
||||
'lib/vm.c',
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -35,7 +57,8 @@ executable('roza',
|
|||
|
||||
executable('roza-tests',
|
||||
sources: [
|
||||
'tests/units/trivial.c'
|
||||
'tests/units/lexer.c',
|
||||
'tests/units/parser.c',
|
||||
],
|
||||
dependencies: [
|
||||
roza_dep,
|
||||
|
|
64
src/main.c
64
src/main.c
|
@ -1,8 +1,64 @@
|
|||
#include <stdio.h>
|
||||
#include <conf.h>
|
||||
#include <getopt.h>
|
||||
#include <commons.h>
|
||||
#include <loader.h>
|
||||
|
||||
int main()
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
printf("Roza v%s\n", ROZA_VERSION);
|
||||
int index = 0;
|
||||
int debug = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
static struct option opts[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"debug", no_argument, 0, 'd'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
char c = getopt_long(argc, argv, "hvd", opts, &index);
|
||||
|
||||
if (c == -1) { break; }
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'h': {
|
||||
printf("Usage: roza [OPTIONS] [SOURCE]\n");
|
||||
printf("OPTIONS:\n");
|
||||
printf("\t-d, --debug\tshow roza debug messages\n");
|
||||
printf("\t-h, --help\tshow this message\n");
|
||||
printf("\t-v, --version\tshow roza version\n");
|
||||
exit(0);
|
||||
} break;
|
||||
|
||||
case 'v': {
|
||||
printf("Roza v%s\n", ROZA_VERSION);
|
||||
printf("License GPLv3+\n");
|
||||
exit(0);
|
||||
} break;
|
||||
|
||||
case 'd': {
|
||||
printf("Roza v%s: Debug mode enabled\n", ROZA_VERSION);
|
||||
debug = 1;
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
{
|
||||
loader_t loader;
|
||||
loader_init(&loader);
|
||||
|
||||
while (optind < argc)
|
||||
{
|
||||
loader_ldfile(&loader, argv[optind++], debug);
|
||||
}
|
||||
|
||||
loader_free(&loader);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
#include <stdarg.h>
|
||||
#include <criterion/criterion.h>
|
||||
#include <lexer.h>
|
||||
|
||||
static void test_lexer_ok(char const* oracle, char const* source)
|
||||
{
|
||||
lexer_t lex;
|
||||
lexer_init(&lex, source, NULL);
|
||||
|
||||
node_t* tok = lexer_try_new_next(&lex);
|
||||
cr_assert(tok != NULL);
|
||||
|
||||
size_t const SZ = 512;
|
||||
char str[SZ];
|
||||
node_str(tok, str, SZ);
|
||||
|
||||
cr_assert_str_eq(str, oracle);
|
||||
|
||||
node_free(tok);
|
||||
lexer_free(&lex);
|
||||
}
|
||||
|
||||
static void test_lexer_ko(char const* source)
|
||||
{
|
||||
lexer_t lex;
|
||||
err_t err;
|
||||
err_init(&err);
|
||||
|
||||
lexer_init(&lex, source, &err);
|
||||
|
||||
node_t* tok = lexer_try_new_next(&lex);
|
||||
|
||||
cr_assert(tok == NULL);
|
||||
cr_assert(err.size > 0);
|
||||
|
||||
err_free(&err);
|
||||
lexer_free(&lex);
|
||||
}
|
||||
|
||||
static void test_lexer(char const* source, size_t n, ...)
|
||||
{
|
||||
va_list lst;
|
||||
va_start(lst, n);
|
||||
|
||||
lexer_t lexer;
|
||||
lexer_init(&lexer, source, NULL);
|
||||
|
||||
for (size_t i=0; i<n; i++)
|
||||
{
|
||||
node_t* tok = lexer_try_new_next(&lexer);
|
||||
cr_assert(NULL != tok);
|
||||
|
||||
size_t const SZ = 256;
|
||||
char tok_str[SZ];
|
||||
node_str(tok, tok_str, SZ);
|
||||
|
||||
char* oracle = va_arg(lst, char*);
|
||||
cr_assert_str_eq(oracle, tok_str);
|
||||
|
||||
node_free(tok);
|
||||
}
|
||||
|
||||
lexer_free(&lexer);
|
||||
va_end(lst);
|
||||
}
|
||||
|
||||
Test(lexer, num) {
|
||||
test_lexer_ok("NUM[35]", " 35 ");
|
||||
test_lexer_ok("NUM[-270]", " -270 ");
|
||||
test_lexer_ok("NUM[0.7]", "0.7");
|
||||
test_lexer_ok("NUM[3.]", "3.");
|
||||
test_lexer_ok("NUM[.4]", ".4");
|
||||
test_lexer_ok("NUM[-3.14]", "-3.14");
|
||||
|
||||
test_lexer("-2 4.1 6", 3,
|
||||
"NUM[-2]",
|
||||
"NUM[4.1]",
|
||||
"NUM[6]");
|
||||
|
||||
test_lexer_ko("-3..14");
|
||||
test_lexer_ko("..2");
|
||||
test_lexer_ko("2..");
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#include <criterion/criterion.h>
|
||||
#include <lexer.h>
|
||||
#include <parser.h>
|
||||
|
||||
static void test_parser_ok(char const* oracle, char const* source)
|
||||
{
|
||||
err_t err;
|
||||
err_init(&err);
|
||||
|
||||
lexer_t lex;
|
||||
lexer_init(&lex, source, &err);
|
||||
|
||||
parser_t parser;
|
||||
parser_init(&parser, &lex, &err);
|
||||
|
||||
node_t* node = parser_try_new_tree(&parser);
|
||||
|
||||
cr_assert(NULL != node);
|
||||
|
||||
size_t const SZ = 256;
|
||||
char msg[SZ];
|
||||
node_str(node, msg, SZ);
|
||||
|
||||
cr_assert_str_eq(msg, oracle);
|
||||
|
||||
node_free(node);
|
||||
free(node);
|
||||
|
||||
parser_free(&parser);
|
||||
lexer_free(&lex);
|
||||
err_free(&err);
|
||||
}
|
||||
|
||||
static void test_parser_ko(char const* source)
|
||||
{
|
||||
err_t err;
|
||||
err_init(&err);
|
||||
|
||||
lexer_t lex;
|
||||
lexer_init(&lex, source, &err);
|
||||
|
||||
parser_t parser;
|
||||
parser_init(&parser, &lex, &err);
|
||||
|
||||
node_t* node = parser_try_new_tree(&parser);
|
||||
|
||||
cr_assert(NULL == node);
|
||||
cr_assert_gt(err.size, 0);
|
||||
|
||||
parser_free(&parser);
|
||||
lexer_free(&lex);
|
||||
err_free(&err);
|
||||
}
|
||||
|
||||
Test(parser, num) {
|
||||
test_parser_ok("MOD", "");
|
||||
test_parser_ok("MOD(NUM[37],NUM[29])", "37 29");
|
||||
|
||||
test_parser_ko(" § ");
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#include <criterion/criterion.h>
|
||||
|
||||
Test(trivial, trivial) {
|
||||
cr_assert(1 + 1 == 2);
|
||||
}
|
Loading…
Reference in New Issue