Files
peppermint_c89/peppermint.c
T

1354 lines
38 KiB
C

/*
* Peppermint C89 interpreter and CLI.
*
* A dependency-free native port of the implemented TypeScript Peppermint
* pieces: the small expression language, project commands, version command,
* and the separate Brainfuck runner/compiler command.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define PM_VERSION "0.0.1"
#define PM_NAME_MAX 128
#define PM_TEXT_MAX 4096
#define PM_MAX_VARS 256
#define PM_PATH_MAX 1024
#define PM_BF_CELLS 30000
#define PM_BF_MAX_PROGRAM 1048576
#define PM_BF_MAX_STACK 65536
typedef enum {
PM_NONE,
PM_INT,
PM_REAL,
PM_STRING
} PMType;
typedef struct {
PMType type;
long integer;
double real;
char *text;
} PMValue;
typedef struct {
char name[PM_NAME_MAX];
PMValue value;
int constant;
} PMVariable;
typedef struct {
const char *source;
const char *filename;
long line;
int argc;
char **argv;
PMVariable vars[PM_MAX_VARS];
int var_count;
int stopped;
} PMContext;
static void pm_free_value(PMValue *value);
static int pm_make_none(PMValue *value);
static int pm_copy_value(PMValue *destination, const PMValue *source);
static int pm_run_source(const char *filename, const char *source, int argc, char **argv);
static char *pm_read_file(const char *filename);
static int pm_write_file(const char *filename, const char *data);
static int pm_file_exists(const char *path);
static int pm_is_dir(const char *path);
static int pm_make_dir(const char *path);
static char *pm_copy_text(const char *start, size_t length);
static char *pm_join_path(const char *left, const char *right);
static char *pm_get_cwd(void);
static char *pm_store_path(void);
static void pm_free_value(PMValue *value)
{
if (value->type == PM_STRING && value->text != NULL) {
free(value->text);
}
value->type = PM_NONE;
value->text = NULL;
value->integer = 0L;
value->real = 0.0;
}
static char *pm_copy_text(const char *start, size_t length)
{
char *result;
result = (char *)malloc(length + 1U);
if (result == NULL) {
return NULL;
}
memcpy(result, start, length);
result[length] = '\0';
return result;
}
static char *pm_strdup_c89(const char *text)
{
return pm_copy_text(text, strlen(text));
}
static void pm_error(PMContext *context, const char *message)
{
fprintf(stderr, "%s:%ld: %s\n", context->filename, context->line, message);
}
static void pm_cli_error(const char *message)
{
fprintf(stderr, "peppermint: %s\n", message);
}
static int pm_make_none(PMValue *value)
{
value->type = PM_NONE;
value->integer = 0L;
value->real = 0.0;
value->text = NULL;
return 1;
}
static int pm_make_integer(PMValue *value, long number)
{
pm_make_none(value);
value->type = PM_INT;
value->integer = number;
return 1;
}
static int pm_make_real(PMValue *value, double number)
{
pm_make_none(value);
value->type = PM_REAL;
value->real = number;
return 1;
}
static int pm_make_string(PMValue *value, const char *start, size_t length)
{
pm_make_none(value);
value->text = pm_copy_text(start, length);
if (value->text == NULL) {
return 0;
}
value->type = PM_STRING;
return 1;
}
static int pm_copy_value(PMValue *destination, const PMValue *source)
{
pm_free_value(destination);
destination->type = source->type;
destination->integer = source->integer;
destination->real = source->real;
destination->text = NULL;
if (source->type == PM_STRING && source->text != NULL) {
destination->text = pm_copy_text(source->text, strlen(source->text));
if (destination->text == NULL) {
destination->type = PM_NONE;
return 0;
}
}
return 1;
}
static void pm_skip_space(PMContext *context, const char **cursor)
{
const char *p;
(void)context;
p = *cursor;
for (;;) {
while (*p == ' ' || *p == '\t' || *p == '\r') {
p++;
}
if (*p == '#') {
while (*p != '\0' && *p != '\n') {
p++;
}
} else {
break;
}
}
*cursor = p;
}
static int pm_is_identifier_start(int c)
{
return isalpha((unsigned char)c) || c == '_';
}
static int pm_is_identifier_char(int c)
{
return isalnum((unsigned char)c) || c == '_';
}
static int pm_match_keyword(const char **cursor, const char *word)
{
const char *p;
size_t length;
p = *cursor;
length = strlen(word);
if (strncmp(p, word, length) == 0 &&
!pm_is_identifier_char((unsigned char)p[length])) {
*cursor = p + length;
return 1;
}
return 0;
}
static PMVariable *pm_find_variable(PMContext *context, const char *name)
{
int i;
for (i = 0; i < context->var_count; i++) {
if (strcmp(context->vars[i].name, name) == 0) {
return &context->vars[i];
}
}
return NULL;
}
static int pm_set_variable(PMContext *context, const char *name,
const PMValue *value, int constant, int declare)
{
PMVariable *variable;
variable = pm_find_variable(context, name);
if (variable == NULL) {
if (!declare) {
pm_error(context, "assignment target is not declared");
return 0;
}
if (context->var_count >= PM_MAX_VARS || strlen(name) >= PM_NAME_MAX) {
pm_error(context, "too many or oversized variables");
return 0;
}
variable = &context->vars[context->var_count];
strcpy(variable->name, name);
pm_make_none(&variable->value);
variable->constant = constant;
context->var_count++;
} else {
if (variable->constant) {
pm_error(context, "cannot assign to constant");
return 0;
}
if (declare) {
variable->constant = constant;
}
}
return pm_copy_value(&variable->value, value);
}
static int pm_parse_expression(PMContext *context, const char **cursor,
PMValue *value);
static int pm_parse_string(PMContext *context, const char **cursor,
PMValue *value)
{
const char *p;
char buffer[PM_TEXT_MAX];
size_t length;
p = *cursor;
p++;
length = 0U;
while (*p != '\0' && *p != '"' && *p != '\n') {
if (*p == '\\' && p[1] != '\0') {
p++;
if (*p == 'n') {
if (length < PM_TEXT_MAX - 1U) buffer[length++] = '\n';
} else if (*p == 'r') {
if (length < PM_TEXT_MAX - 1U) buffer[length++] = '\r';
} else if (*p == 't') {
if (length < PM_TEXT_MAX - 1U) buffer[length++] = '\t';
} else {
if (length < PM_TEXT_MAX - 1U) buffer[length++] = *p;
}
p++;
} else {
if (length < PM_TEXT_MAX - 1U) buffer[length++] = *p;
p++;
}
}
if (*p != '"') {
pm_error(context, "string not closed");
return 0;
}
buffer[length] = '\0';
if (!pm_make_string(value, buffer, length)) {
pm_error(context, "out of memory");
return 0;
}
*cursor = p + 1;
return 1;
}
static int pm_parse_primary(PMContext *context, const char **cursor,
PMValue *value)
{
const char *p;
char name[PM_NAME_MAX];
size_t length;
char *end;
long integer;
double real;
PMVariable *variable;
int negative;
p = *cursor;
pm_skip_space(context, &p);
negative = 0;
if (*p == '-') {
p++;
pm_skip_space(context, &p);
negative = 1;
}
if (*p == '"' && !negative) {
if (!pm_parse_string(context, &p, value)) return 0;
*cursor = p;
return 1;
}
if (*p == '(' && !negative) {
p++;
if (!pm_parse_expression(context, &p, value)) return 0;
pm_skip_space(context, &p);
if (*p != ')') {
pm_error(context, "expected ')'");
pm_free_value(value);
return 0;
}
*cursor = p + 1;
return 1;
}
if (isdigit((unsigned char)*p) || (*p == '.' && isdigit((unsigned char)p[1]))) {
real = strtod(p, &end);
if (end == p) {
pm_error(context, "invalid number");
return 0;
}
if (negative) real = -real;
if (strchr(p, '.') != NULL && strchr(p, '.') < end) {
pm_make_real(value, real);
} else {
integer = strtol(p, &end, 10);
if (negative) integer = -integer;
pm_make_integer(value, integer);
}
*cursor = end;
return 1;
}
if (pm_is_identifier_start((unsigned char)*p) && !negative) {
length = 0U;
while (pm_is_identifier_char((unsigned char)p[length])) {
if (length < PM_NAME_MAX - 1U) name[length] = p[length];
length++;
}
if (length >= PM_NAME_MAX) {
pm_error(context, "identifier too long");
return 0;
}
name[length] = '\0';
if (strcmp(name, "None") == 0) {
pm_make_none(value);
*cursor = p + length;
return 1;
}
variable = pm_find_variable(context, name);
if (variable == NULL) {
pm_error(context, "unknown identifier");
return 0;
}
if (!pm_copy_value(value, &variable->value)) {
pm_error(context, "out of memory");
return 0;
}
*cursor = p + length;
return 1;
}
pm_error(context, "expected expression");
return 0;
}
static int pm_numeric(const PMValue *value, double *number)
{
if (value->type == PM_INT) {
*number = (double)value->integer;
return 1;
}
if (value->type == PM_REAL) {
*number = value->real;
return 1;
}
return 0;
}
static int pm_apply_arithmetic(PMContext *context, PMValue *left, char operator,
PMValue *right)
{
double a;
double b;
double result;
int integer_result;
char *text;
size_t left_length;
size_t right_length;
long ia;
long ib;
if (operator == '+' && left->type == PM_STRING && right->type == PM_STRING) {
left_length = strlen(left->text);
right_length = strlen(right->text);
text = (char *)malloc(left_length + right_length + 1U);
if (text == NULL) {
pm_error(context, "out of memory");
return 0;
}
memcpy(text, left->text, left_length);
memcpy(text + left_length, right->text, right_length + 1U);
pm_free_value(left);
left->type = PM_STRING;
left->text = text;
return 1;
}
if (!pm_numeric(left, &a) || !pm_numeric(right, &b)) {
pm_error(context, "arithmetic requires numeric values");
return 0;
}
if (operator == '+') result = a + b;
else if (operator == '-') result = a - b;
else if (operator == '*') result = a * b;
else if (operator == '/') {
if (b == 0.0) {
pm_error(context, "division by zero");
return 0;
}
result = a / b;
} else if (operator == '%') {
if (left->type != PM_INT || right->type != PM_INT || right->integer == 0L) {
pm_error(context, "modulo requires nonzero integers");
return 0;
}
ia = left->integer;
ib = right->integer;
pm_free_value(left);
pm_make_integer(left, ia % ib);
return 1;
} else {
pm_error(context, "unknown operator");
return 0;
}
integer_result = left->type == PM_INT && right->type == PM_INT && operator != '/';
pm_free_value(left);
if (integer_result) {
pm_make_integer(left, (long)result);
} else {
pm_make_real(left, result);
}
return 1;
}
static int pm_parse_term(PMContext *context, const char **cursor, PMValue *value)
{
const char *p;
PMValue right;
char operator;
p = *cursor;
pm_make_none(value);
if (!pm_parse_primary(context, &p, value)) return 0;
for (;;) {
pm_skip_space(context, &p);
operator = *p;
if (operator != '*' && operator != '/' && operator != '%') break;
p++;
pm_make_none(&right);
if (!pm_parse_primary(context, &p, &right)) {
pm_free_value(value);
return 0;
}
if (!pm_apply_arithmetic(context, value, operator, &right)) {
pm_free_value(value);
pm_free_value(&right);
return 0;
}
pm_free_value(&right);
}
*cursor = p;
return 1;
}
static int pm_parse_sum(PMContext *context, const char **cursor, PMValue *value)
{
const char *p;
PMValue right;
char operator;
p = *cursor;
pm_make_none(value);
if (!pm_parse_term(context, &p, value)) return 0;
for (;;) {
pm_skip_space(context, &p);
operator = *p;
if (operator != '+' && operator != '-') break;
p++;
pm_make_none(&right);
if (!pm_parse_term(context, &p, &right)) {
pm_free_value(value);
return 0;
}
if (!pm_apply_arithmetic(context, value, operator, &right)) {
pm_free_value(value);
pm_free_value(&right);
return 0;
}
pm_free_value(&right);
}
*cursor = p;
return 1;
}
static int pm_compare_values(PMContext *context, PMValue *left, const char *op,
PMValue *right)
{
double a;
double b;
int result;
result = 0;
if (pm_numeric(left, &a) && pm_numeric(right, &b)) {
if (strcmp(op, "==") == 0) result = (a == b);
else if (strcmp(op, "!=") == 0) result = (a != b);
else if (strcmp(op, ">=") == 0) result = (a >= b);
else if (strcmp(op, "<=") == 0) result = (a <= b);
else if (strcmp(op, ">") == 0) result = (a > b);
else if (strcmp(op, "<") == 0) result = (a < b);
} else if (left->type == PM_STRING && right->type == PM_STRING) {
int cmp;
cmp = strcmp(left->text, right->text);
if (strcmp(op, "==") == 0) result = (cmp == 0);
else if (strcmp(op, "!=") == 0) result = (cmp != 0);
else if (strcmp(op, ">=") == 0) result = (cmp >= 0);
else if (strcmp(op, "<=") == 0) result = (cmp <= 0);
else if (strcmp(op, ">") == 0) result = (cmp > 0);
else if (strcmp(op, "<") == 0) result = (cmp < 0);
} else if ((left->type == PM_NONE || right->type == PM_NONE) &&
(strcmp(op, "==") == 0 || strcmp(op, "!=") == 0)) {
result = (left->type == right->type);
if (strcmp(op, "!=") == 0) result = !result;
} else {
pm_error(context, "comparison requires matching numeric or string values");
return 0;
}
pm_free_value(left);
pm_make_integer(left, (long)result);
return 1;
}
static int pm_parse_expression(PMContext *context, const char **cursor,
PMValue *value)
{
const char *p;
char op[3];
PMValue right;
p = *cursor;
pm_make_none(value);
if (!pm_parse_sum(context, &p, value)) return 0;
for (;;) {
pm_skip_space(context, &p);
op[0] = '\0';
op[1] = '\0';
op[2] = '\0';
if ((p[0] == '=' && p[1] == '=') || (p[0] == '!' && p[1] == '=') ||
(p[0] == '>' && p[1] == '=') || (p[0] == '<' && p[1] == '=')) {
op[0] = p[0];
op[1] = p[1];
p += 2;
} else if (p[0] == '>' || p[0] == '<') {
op[0] = p[0];
p++;
} else {
break;
}
pm_make_none(&right);
if (!pm_parse_sum(context, &p, &right)) {
pm_free_value(value);
return 0;
}
if (!pm_compare_values(context, value, op, &right)) {
pm_free_value(value);
pm_free_value(&right);
return 0;
}
pm_free_value(&right);
}
*cursor = p;
return 1;
}
static void pm_print_value(const PMValue *value)
{
if (value->type == PM_STRING) printf("%s\n", value->text == NULL ? "" : value->text);
else if (value->type == PM_INT) printf("%ld\n", value->integer);
else if (value->type == PM_REAL) printf("%g\n", value->real);
else printf("None\n");
}
static int pm_builtin(PMContext *context, const char **cursor)
{
const char *p;
const char *start;
char name[PM_NAME_MAX];
char *end;
long index;
size_t length;
const char *result;
int i;
p = *cursor + 1;
start = p;
while (pm_is_identifier_char((unsigned char)*p)) p++;
length = (size_t)(p - start);
if (length == 0U || length >= PM_NAME_MAX) {
pm_error(context, "invalid builtin name");
return 0;
}
memcpy(name, start, length);
name[length] = '\0';
if (strcmp(name, "exit") == 0) {
context->stopped = 1;
*cursor = p;
return 1;
}
if (strcmp(name, "argv") == 0) {
if (*p == ':') {
index = strtol(p + 1, &end, 10);
if (end == p + 1 || index < 0L || index >= context->argc) {
printf("None\n");
} else {
printf("%s\n", context->argv[index]);
}
p = end;
} else {
for (i = 0; i < context->argc; i++) {
if (i != 0) putchar(' ');
fputs(context->argv[i], stdout);
}
putchar('\n');
}
} else if (strcmp(name, "env") == 0) {
result = NULL;
if (*p == ':') {
p++;
start = p;
while (pm_is_identifier_char((unsigned char)*p)) p++;
length = (size_t)(p - start);
if (length >= PM_NAME_MAX) length = PM_NAME_MAX - 1U;
memcpy(name, start, length);
name[length] = '\0';
result = getenv(name);
}
printf("%s\n", result == NULL || result[0] == '\0' ? "None" : result);
} else {
pm_error(context, "unknown builtin");
return 0;
}
*cursor = p;
return 1;
}
static int pm_read_identifier(PMContext *context, const char **cursor, char *name)
{
const char *p;
size_t length;
p = *cursor;
pm_skip_space(context, &p);
if (!pm_is_identifier_start((unsigned char)*p)) {
pm_error(context, "expected variable name");
return 0;
}
length = 0U;
while (pm_is_identifier_char((unsigned char)p[length])) {
if (length < PM_NAME_MAX - 1U) name[length] = p[length];
length++;
}
if (length >= PM_NAME_MAX) {
pm_error(context, "identifier too long");
return 0;
}
name[length] = '\0';
*cursor = p + length;
return 1;
}
static int pm_statement_end(PMContext *context, const char **cursor)
{
const char *p;
p = *cursor;
while (*p == ' ' || *p == '\t' || *p == '\r') p++;
if (*p == '#') {
while (*p != '\0' && *p != '\n') p++;
}
if (*p == '\n') {
context->line++;
p++;
} else if (*p != '\0') {
pm_error(context, "unexpected characters");
return 0;
}
*cursor = p;
return 1;
}
static int pm_run(PMContext *context)
{
const char *p;
const char *mark;
PMValue value;
char name[PM_NAME_MAX];
int declare;
int constant;
p = context->source;
context->line = 1L;
while (*p != '\0' && !context->stopped) {
pm_skip_space(context, &p);
if (*p == '\n') {
context->line++;
p++;
continue;
}
if (*p == '\0') break;
if (*p == '@') {
if (!pm_builtin(context, &p)) return 0;
} else {
declare = 0;
constant = 0;
mark = p;
if (pm_match_keyword(&p, "let")) {
declare = 1;
if (!pm_read_identifier(context, &p, name)) return 0;
pm_skip_space(context, &p);
if (p[0] == ':' && p[1] == '=') {
constant = 1;
p += 2;
} else if (p[0] == '=') {
constant = 0;
p++;
} else {
pm_error(context, "expected ':=' or '='");
return 0;
}
pm_make_none(&value);
if (!pm_parse_expression(context, &p, &value)) return 0;
if (!pm_set_variable(context, name, &value, constant, declare)) {
pm_free_value(&value);
return 0;
}
pm_free_value(&value);
} else if (pm_is_identifier_start((unsigned char)*p)) {
if (!pm_read_identifier(context, &p, name)) return 0;
pm_skip_space(context, &p);
if (*p == '=' && p[1] != '=') {
p++;
pm_make_none(&value);
if (!pm_parse_expression(context, &p, &value)) return 0;
if (!pm_set_variable(context, name, &value, 0, 0)) {
pm_free_value(&value);
return 0;
}
pm_free_value(&value);
} else {
p = mark;
pm_make_none(&value);
if (!pm_parse_expression(context, &p, &value)) return 0;
pm_print_value(&value);
pm_free_value(&value);
}
} else {
pm_make_none(&value);
if (!pm_parse_expression(context, &p, &value)) return 0;
pm_print_value(&value);
pm_free_value(&value);
}
}
if (!pm_statement_end(context, &p)) return 0;
}
return 1;
}
static int pm_run_source(const char *filename, const char *source, int argc, char **argv)
{
PMContext context;
int i;
int result;
context.source = source;
context.filename = filename;
context.argc = argc;
context.argv = argv;
context.var_count = 0;
context.stopped = 0;
context.line = 1L;
for (i = 0; i < PM_MAX_VARS; i++) {
context.vars[i].name[0] = '\0';
context.vars[i].constant = 0;
pm_make_none(&context.vars[i].value);
}
result = pm_run(&context) ? 0 : 1;
for (i = 0; i < context.var_count; i++) pm_free_value(&context.vars[i].value);
return result;
}
static char *pm_read_file(const char *filename)
{
FILE *file;
long size;
char *data;
size_t count;
file = fopen(filename, "rb");
if (file == NULL) return NULL;
if (fseek(file, 0L, SEEK_END) != 0) { fclose(file); return NULL; }
size = ftell(file);
if (size < 0L || fseek(file, 0L, SEEK_SET) != 0) { fclose(file); return NULL; }
data = (char *)malloc((size_t)size + 1U);
if (data == NULL) { fclose(file); return NULL; }
count = fread(data, 1U, (size_t)size, file);
fclose(file);
data[count] = '\0';
return data;
}
static int pm_write_file(const char *filename, const char *data)
{
FILE *file;
file = fopen(filename, "wb");
if (file == NULL) return 0;
if (fwrite(data, 1U, strlen(data), file) != strlen(data)) {
fclose(file);
return 0;
}
fclose(file);
return 1;
}
static int pm_file_exists(const char *path)
{
struct stat st;
return stat(path, &st) == 0;
}
static int pm_is_dir(const char *path)
{
struct stat st;
if (stat(path, &st) != 0) return 0;
return S_ISDIR(st.st_mode) ? 1 : 0;
}
static int pm_make_dir(const char *path)
{
if (mkdir(path, 0777) == 0) return 1;
if (errno == EEXIST && pm_is_dir(path)) return 1;
return 0;
}
static char *pm_join_path(const char *left, const char *right)
{
size_t l;
size_t r;
char *out;
int slash;
l = strlen(left);
r = strlen(right);
slash = (l > 0U && left[l - 1U] == '/') ? 0 : 1;
out = (char *)malloc(l + r + (size_t)slash + 1U);
if (out == NULL) return NULL;
strcpy(out, left);
if (slash) strcat(out, "/");
strcat(out, right);
return out;
}
static char *pm_get_cwd(void)
{
char buffer[PM_PATH_MAX];
if (getcwd(buffer, sizeof(buffer)) == NULL) return NULL;
return pm_strdup_c89(buffer);
}
static char *pm_store_path(void)
{
const char *env;
const char *home;
char *dir;
char *path;
env = getenv("PEPPERMINT_STORE");
if (env != NULL && env[0] != '\0') return pm_strdup_c89(env);
home = getenv("HOME");
if (home == NULL || home[0] == '\0') home = ".";
dir = pm_join_path(home, ".peppermint");
if (dir == NULL) return NULL;
pm_make_dir(dir);
path = pm_join_path(dir, "store.json");
free(dir);
return path;
}
static const char *pm_flag_value(int argc, char **argv, const char *name)
{
int i;
size_t n;
n = strlen(name);
for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "--", 2U) == 0 && strncmp(argv[i] + 2, name, n) == 0 && argv[i][2 + n] == '=') {
return argv[i] + 3 + n;
}
}
return NULL;
}
static int pm_true_flag(const char *text)
{
if (text == NULL) return 0;
return strcmp(text, "true") == 0 || strcmp(text, "True") == 0 || strcmp(text, "TRUE") == 0 || strcmp(text, "1") == 0;
}
static int pm_validate_flags(int argc, char **argv, const char *allowed)
{
int i;
char name[PM_NAME_MAX];
size_t j;
const char *p;
for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "--", 2U) != 0) continue;
p = argv[i] + 2;
j = 0U;
while (p[j] != '\0' && p[j] != '=' && j < PM_NAME_MAX - 1U) {
name[j] = p[j];
j++;
}
name[j] = '\0';
if (strcmp(name, "args") == 0) continue;
if (strstr(allowed, name) == NULL) {
fprintf(stderr, "peppermint: invalid flag --%s\n", name);
return 0;
}
if (p[j] != '=') {
fprintf(stderr, "peppermint: expected --%s=<value>\n", name);
return 0;
}
}
return 1;
}
static int pm_run_file(const char *filename, int argc, char **argv)
{
char *source;
int result;
source = pm_read_file(filename);
if (source == NULL) {
fprintf(stderr, "peppermint: cannot read %s\n", filename);
return 2;
}
result = pm_run_source(filename, source, argc, argv);
free(source);
return result;
}
static char *pm_json_string_value(const char *json, const char *key)
{
char pattern[PM_NAME_MAX + 4];
const char *p;
const char *start;
char *out;
size_t length;
strcpy(pattern, "\"");
strcat(pattern, key);
strcat(pattern, "\"");
p = strstr(json, pattern);
if (p == NULL) return NULL;
p += strlen(pattern);
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
if (*p != ':') return NULL;
p++;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
if (*p != '"') return NULL;
p++;
start = p;
while (*p != '\0' && *p != '"') p++;
if (*p != '"') return NULL;
length = (size_t)(p - start);
out = pm_copy_text(start, length);
return out;
}
static char *pm_project_main(void)
{
char *cwd;
char *config_path;
char *json;
char *main_file;
cwd = pm_get_cwd();
if (cwd == NULL) return NULL;
config_path = pm_join_path(cwd, ".peppermint");
free(cwd);
if (config_path == NULL) return NULL;
json = pm_read_file(config_path);
free(config_path);
if (json == NULL) {
pm_cli_error("cannot find peppermint config files");
return NULL;
}
main_file = pm_json_string_value(json, "main");
free(json);
if (main_file == NULL || main_file[0] == '\0') {
if (main_file != NULL) free(main_file);
pm_cli_error("cannot find entry point in .peppermint");
return NULL;
}
return main_file;
}
static int pm_command_run(int argc, char **argv)
{
const char *file;
char *project_file;
int args_index;
int i;
file = pm_flag_value(argc, argv, "file");
if (!pm_validate_flags(argc, argv, "file")) return 2;
args_index = argc;
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "--args") == 0) {
args_index = i + 1;
break;
}
}
if (file == NULL) {
project_file = pm_project_main();
if (project_file == NULL) return 2;
i = pm_run_file(project_file, argc - args_index, argv + args_index);
free(project_file);
return i;
}
return pm_run_file(file, argc - args_index, argv + args_index);
}
static int pm_append_project_store(const char *name, const char *path)
{
char *store;
FILE *file;
time_t now;
struct tm *tm_now;
int day;
int month;
int year;
store = pm_store_path();
if (store == NULL) return 0;
file = fopen(store, "a");
if (file == NULL) {
free(store);
return 0;
}
now = time(NULL);
tm_now = localtime(&now);
if (tm_now == NULL) {
day = 0;
month = 0;
year = 0;
} else {
day = tm_now->tm_mday;
month = tm_now->tm_mon;
year = tm_now->tm_year + 1900;
}
fprintf(file, "%s\t%s\t%d-%d-%d\n", name, path, day, month, year);
fclose(file);
free(store);
return 1;
}
static int pm_command_init(int argc, char **argv)
{
const char *name;
const char *author;
const char *license;
char *cwd;
char *path;
char *tests_dir;
char *readme_path;
char *app_path;
char *test_path;
char *config_path;
char *main_path;
char readme[PM_TEXT_MAX];
char config[PM_TEXT_MAX];
if (!pm_validate_flags(argc, argv, "name license author")) return 2;
name = pm_flag_value(argc, argv, "name");
author = pm_flag_value(argc, argv, "author");
license = pm_flag_value(argc, argv, "license");
if (name == NULL || name[0] == '\0') {
pm_cli_error("--name is a required flag for init");
return 2;
}
cwd = pm_get_cwd();
if (cwd == NULL) return 2;
if (strcmp(name, ".") == 0) path = pm_strdup_c89(cwd);
else path = pm_join_path(cwd, name);
free(cwd);
if (path == NULL) return 2;
if (pm_file_exists(path) && !pm_is_dir(path)) {
pm_cli_error("project path exists and is not a directory");
free(path);
return 2;
}
if (!pm_file_exists(path) && !pm_make_dir(path)) {
pm_cli_error("cannot create project directory");
free(path);
return 2;
}
tests_dir = pm_join_path(path, "tests");
if (tests_dir == NULL || !pm_make_dir(tests_dir)) {
pm_cli_error("cannot create tests directory");
free(path);
if (tests_dir != NULL) free(tests_dir);
return 2;
}
readme_path = pm_join_path(path, "README.md");
app_path = pm_join_path(path, "app.ppm");
test_path = pm_join_path(tests_dir, "test.ppm");
config_path = pm_join_path(path, ".peppermint");
main_path = pm_join_path(path, "app.ppm");
free(tests_dir);
if (readme_path == NULL || app_path == NULL || test_path == NULL || config_path == NULL || main_path == NULL) {
pm_cli_error("out of memory");
free(path);
return 2;
}
sprintf(readme, "# %s\n", name);
sprintf(config, "{\n \"name\": \"%s\",\n \"path\": \"%s\",\n \"author\": \"%s\",\n \"license\": \"%s\",\n \"main\": \"%s\"\n}\n",
name, path, author == NULL ? "" : author, license == NULL ? "" : license, main_path);
if (!pm_write_file(readme_path, readme) || !pm_write_file(app_path, " ") ||
!pm_write_file(test_path, "") || !pm_write_file(config_path, config)) {
pm_cli_error("cannot write project files");
free(path); free(readme_path); free(app_path); free(test_path); free(config_path); free(main_path);
return 2;
}
pm_append_project_store(name, path);
printf("Created Peppermint project %s at %s\n", name, path);
free(path); free(readme_path); free(app_path); free(test_path); free(config_path); free(main_path);
return 0;
}
static int pm_command_list(void)
{
char *store;
FILE *file;
char line[PM_TEXT_MAX];
int count;
char *name;
char *path;
char *date;
store = pm_store_path();
if (store == NULL) return 2;
file = fopen(store, "r");
if (file == NULL) {
printf("It seems like you haven't created any project\n");
free(store);
return 0;
}
printf("NAME\tPATH\tCREATED\n");
count = 0;
while (fgets(line, sizeof(line), file) != NULL) {
name = strtok(line, "\t");
path = strtok(NULL, "\t");
date = strtok(NULL, "\t\r\n");
if (name != NULL && path != NULL && date != NULL) {
printf("%s\t%s\t%s\n", name, path, date);
count++;
}
}
fclose(file);
free(store);
if (count == 0) printf("It seems like you haven't created any project\n");
return 0;
}
static int pm_bf_compile(const char *source)
{
int cells[PM_BF_CELLS];
int pointer;
const char *p;
int value;
char compiled[PM_TEXT_MAX];
char part[64];
char *dir;
char *out;
size_t used;
int i;
for (i = 0; i < PM_BF_CELLS; i++) cells[i] = 0;
pointer = 0;
value = 0;
compiled[0] = '\0';
used = 0U;
p = source;
while (*p != '\0') {
if (*p == '+') cells[pointer]++;
else if (*p == '-') cells[pointer]--;
else if (*p == '>' && pointer < PM_BF_CELLS - 1) pointer++;
else if (*p == '<' && pointer > 0) pointer--;
else if (*p == '.') {
int diff;
diff = cells[pointer] - value;
value = cells[pointer];
if (diff < 0) sprintf(part, "%d-", -diff);
else sprintf(part, "%d+", diff);
if (used + strlen(part) < sizeof(compiled)) {
strcat(compiled, part);
used += strlen(part);
}
}
p++;
}
dir = pm_join_path(".", "dist");
if (dir == NULL) return 0;
pm_make_dir(dir);
out = pm_join_path(dir, "test.long");
free(dir);
if (out == NULL) return 0;
i = pm_write_file(out, compiled);
free(out);
return i;
}
static int pm_bf_execute(const char *source)
{
unsigned char *cells;
long *jumps;
long *stack;
char *program;
long pc;
long dp;
long length;
long top;
long i;
int ch;
cells = (unsigned char *)calloc((size_t)PM_BF_CELLS, 1U);
jumps = (long *)malloc((size_t)PM_BF_MAX_PROGRAM * sizeof(long));
stack = (long *)malloc((size_t)PM_BF_MAX_STACK * sizeof(long));
program = (char *)malloc((size_t)PM_BF_MAX_PROGRAM);
if (cells == NULL || jumps == NULL || stack == NULL || program == NULL) {
free(cells); free(jumps); free(stack); free(program);
pm_cli_error("out of memory");
return 1;
}
length = 0L;
for (i = 0L; source[i] != '\0'; i++) {
if (strchr("[].,<>+-", source[i]) != NULL) {
if (length >= PM_BF_MAX_PROGRAM) {
pm_cli_error("bf program too large");
free(cells); free(jumps); free(stack); free(program);
return 1;
}
program[length] = source[i];
jumps[length] = -1L;
length++;
}
}
top = 0L;
for (i = 0L; i < length; i++) {
if (program[i] == '[') {
if (top >= PM_BF_MAX_STACK) { pm_cli_error("bf bracket nesting too deep"); free(cells); free(jumps); free(stack); free(program); return 1; }
stack[top++] = i;
} else if (program[i] == ']') {
long start;
if (top == 0L) { pm_cli_error("unmatched bf ']' "); free(cells); free(jumps); free(stack); free(program); return 1; }
start = stack[--top];
jumps[start] = i;
jumps[i] = start;
}
}
if (top != 0L) { pm_cli_error("unmatched bf '[' "); free(cells); free(jumps); free(stack); free(program); return 1; }
pc = 0L;
dp = 0L;
while (pc < length) {
if (program[pc] == '+') { cells[dp]++; pc++; }
else if (program[pc] == '-') { cells[dp]--; pc++; }
else if (program[pc] == '>') { if (dp < PM_BF_CELLS - 1) dp++; pc++; }
else if (program[pc] == '<') { if (dp > 0) dp--; pc++; }
else if (program[pc] == '.') { putchar((int)cells[dp]); pc++; }
else if (program[pc] == ',') { ch = getchar(); cells[dp] = (unsigned char)(ch == EOF ? 0 : ch); pc++; }
else if (program[pc] == '[') { if (cells[dp] == 0) pc = jumps[pc] + 1L; else pc++; }
else if (program[pc] == ']') { if (cells[dp] != 0) pc = jumps[pc] + 1L; else pc++; }
else pc++;
}
putchar('\n');
free(cells); free(jumps); free(stack); free(program);
return 0;
}
static int pm_command_bf(int argc, char **argv)
{
const char *file;
const char *compile;
char *source;
int result;
if (!pm_validate_flags(argc, argv, "file compile")) return 2;
file = pm_flag_value(argc, argv, "file");
compile = pm_flag_value(argc, argv, "compile");
if (file == NULL || file[0] == '\0') {
pm_cli_error("bf requires --file=<path>");
return 2;
}
source = pm_read_file(file);
if (source == NULL) {
fprintf(stderr, "peppermint: %s - file not found\n", file);
return 2;
}
if (pm_true_flag(compile) && !pm_bf_compile(source)) {
pm_cli_error("unable to write compiled bf output");
free(source);
return 1;
}
result = pm_bf_execute(source);
free(source);
return result;
}
static void pm_usage(void)
{
printf("Peppermint C89 %s\n", PM_VERSION);
printf("usage:\n");
printf(" peppermint run [--file=FILE] [--args ARGS...]\n");
printf(" peppermint init --name=NAME [--author=AUTHOR] [--license=LICENSE]\n");
printf(" peppermint list\n");
printf(" peppermint version\n");
printf(" peppermint bf --file=FILE [--compile=true]\n");
printf(" peppermint FILE.ppm [ARGS...]\n");
}
int main(int argc, char **argv)
{
if (argc < 2) {
pm_usage();
return 2;
}
if (strcmp(argv[1], "run") == 0) return pm_command_run(argc - 2, argv + 2);
if (strcmp(argv[1], "init") == 0) return pm_command_init(argc - 2, argv + 2);
if (strcmp(argv[1], "list") == 0) return pm_command_list();
if (strcmp(argv[1], "version") == 0) {
#if defined(__linux__)
printf("Peppermint Version %s[linux]\n", PM_VERSION);
#elif defined(_WIN32)
printf("Peppermint Version %s[win32]\n", PM_VERSION);
#elif defined(__APPLE__)
printf("Peppermint Version %s[darwin]\n", PM_VERSION);
#else
printf("Peppermint Version %s[unknown]\n", PM_VERSION);
#endif
return 0;
}
if (strcmp(argv[1], "bf") == 0) return pm_command_bf(argc - 2, argv + 2);
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) {
pm_usage();
return 0;
}
return pm_run_file(argv[1], argc - 2, argv + 2);
}