Files
peppermint_c89/c89/peppermint.c
T

608 lines
16 KiB
C

/*
* Peppermint C89 interpreter.
*
* This implements the language behavior present in the original TypeScript
* front end: let bindings, integer/real literals, strings, arithmetic,
* comments, and @argv/@env/@exit builtins.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define PM_NAME_MAX 128
#define PM_TEXT_MAX 4096
#define PM_MAX_VARS 256
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;
} 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)
{
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 void pm_error(PMContext *context, const char *message)
{
fprintf(stderr, "%s:%ld: %s\n", context->filename, context->line, message);
}
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(PMContext *context, const char **cursor, const char *word)
{
const char *p;
size_t length;
(void)context;
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 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) {
if (source->text == NULL) {
return 1;
}
destination->text = pm_copy_text(source->text, strlen(source->text));
if (destination->text == NULL) {
destination->type = PM_NONE;
return 0;
}
}
return 1;
}
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)
{
PMVariable *variable;
variable = pm_find_variable(context, name);
if (variable == NULL) {
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);
context->var_count++;
}
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;
const char *start;
char buffer[PM_TEXT_MAX];
size_t length;
p = *cursor;
p++;
start = p;
length = 0U;
while (*p != '\0' && *p != '"') {
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++;
}
}
(void)start;
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;
p = *cursor;
pm_skip_space(context, &p);
if (*p == '"') {
if (!pm_parse_string(context, &p, value)) return 0;
*cursor = p;
return 1;
}
if (*p == '(') {
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 (strchr(p, '.') != NULL && strchr(p, '.') < end) {
pm_make_real(value, real);
} else {
integer = strtol(p, &end, 10);
pm_make_integer(value, integer);
}
*cursor = end;
return 1;
}
if (pm_is_identifier_start((unsigned char)*p)) {
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';
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_operator(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;
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 == '%') {
long ia;
long ib;
if (left->type != PM_INT || right->type != PM_INT || b == 0.0) {
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_expression(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 != '*' &&
operator != '/' && operator != '%') break;
p++;
pm_make_none(&right);
if (!pm_parse_primary(context, &p, &right)) {
pm_free_value(value);
return 0;
}
if (!pm_apply_operator(context, value, operator, &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);
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 void 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;
p = *cursor + 1;
start = p;
while (pm_is_identifier_char((unsigned char)*p)) p++;
length = (size_t)(p - start);
if (length >= PM_NAME_MAX) {
pm_error(context, "builtin name too long");
return;
}
memcpy(name, start, length);
name[length] = '\0';
if (strcmp(name, "exit") == 0) {
context->stopped = 1;
*cursor = p;
return;
}
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 {
int i;
for (i = 0; i < context->argc; i++) {
if (i != 0) putchar(' ');
fputs(context->argv[i], stdout);
}
putchar('\n');
}
} else if (strcmp(name, "env") == 0) {
const char *key;
const char *result;
key = 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';
key = name;
}
result = key == NULL ? NULL : getenv(key);
printf("%s\n", result == NULL || result[0] == '\0' ? "None" : result);
} else {
pm_error(context, "unknown builtin");
}
*cursor = p;
}
static int pm_run(PMContext *context)
{
const char *p;
PMValue value;
char name[PM_NAME_MAX];
size_t length;
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 == '@') {
pm_builtin(context, &p);
} else if (pm_match(context, &p, "let")) {
pm_skip_space(context, &p);
length = 0U;
while (pm_is_identifier_char((unsigned char)p[length])) {
if (length < PM_NAME_MAX - 1U) name[length] = p[length];
length++;
}
if (length == 0U || length >= PM_NAME_MAX) {
pm_error(context, "expected variable name");
return 0;
}
name[length] = '\0';
p += length;
pm_skip_space(context, &p);
if (p[0] != ':' || p[1] != '=') {
pm_error(context, "expected ':='");
return 0;
}
p += 2;
pm_make_none(&value);
if (!pm_parse_expression(context, &p, &value)) return 0;
if (!pm_set_variable(context, name, &value)) {
pm_free_value(&value);
return 0;
}
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);
}
while (*p == ' ' || *p == '\t' || *p == '\r') p++;
if (*p == '\n') {
context->line++;
p++;
} else if (*p != '\0' && *p != '#') {
pm_error(context, "unexpected characters");
return 0;
}
}
return 1;
}
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;
}
int main(int argc, char **argv)
{
PMContext context;
char *source;
int i;
int result;
if (argc < 2) {
fprintf(stderr, "usage: peppermint <file.ppm> [args...]\n");
return 2;
}
source = pm_read_file(argv[1]);
if (source == NULL) {
fprintf(stderr, "peppermint: cannot read %s\n", argv[1]);
return 2;
}
context.source = source;
context.filename = argv[1];
context.argc = argc - 2;
context.argv = argv + 2;
context.var_count = 0;
context.stopped = 0;
for (i = 0; i < PM_MAX_VARS; i++) 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);
free(source);
return result;
}