Move C89 port to repository root
This commit is contained in:
+607
@@ -0,0 +1,607 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user