/* * 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 #include #include #include #include #include #include #include #include #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=\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="); 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); }