8
\$\begingroup\$

As the title states, this is a compiler written in C with a ruby build script that translates a minimal LISP dialect and spits out an executable jar file. I designed this LISP dialect and named it mjLisp. It has some missing features (that requires some extra parsing effort, thus more code) as you can see in the list below.

  • lambda (anonymous functions)
  • let (scoped variables)
  • begin (imperative sequencing; currently there is an extra main function to do this)
  • macros
  • error handling/exceptions
  • variadic functions

One obvious problem that I can pick out is that the parsing and translating code is somewhat boilerplate, but there may happily be a lot more problems. If you can help me improving the code please don't hesitate.


test.mjl

(def nil (list))

(def (~= a b)
    (~ (= a b))
)

(def (>= a b)
    (~ (< a b))
)

(def (<= a b)
    (~ (> a b))
)

(def (reverse* a b)
    (if (= a nil)
        b
        (reverse* (cdr a) (cons (car a) b))
    )
)

(def (reverse l)
    (reverse* l nil)
)

(def (fac n)
    (if (< n 2)
        1
        (* n (fac (- n 1)))
    )
)

(def (P a b)
    (/ (fac a) (fac (- a b)))
)

(def (C a b)
    (/ (P a b) (P b b))
)

(def (pascal's-triangle-row* n n')
    (if (< n' 0)
        nil
        (cons (C n n') (pascal's-triangle-row* n (- n' 1)))
    )
)

(def (pascal's-triangle-row n)
    (pascal's-triangle-row* n n)
)

(def (pascal's-triangle* n)
    (if (< n 0)
        nil
        (cons (pascal's-triangle-row n) (pascal's-triangle* (- n 1)))
    )
)

(def (pascal's-triangle n)
    (reverse (pascal's-triangle* n))
)

(main
    (print-line (pascal's-triangle 10))
)

Output

((1) (1 1) (1 2 1) (1 3 3 1) (1 4 6 4 1) (1 5 10 10 5 1) (1 6 15 20 15 6 1) (1 7 21 35 35 21 7 1) (1 8 28 56 70 56 28 8 1) (1 9 36 84
126 126 84 36 9 1) (1 10 45 120 210 252 210 120 45 10 1))

mjlc.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <glib/gstdio.h>

#define STRLEN 1000
#define E(WHERE, FUNC) perror("error: "WHERE"."FUNC)

bool is_start_of_identifier(char c) {
    return isalpha(c) || c == '\'' || c == '?' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == '=' || c == '>' || c == '<' || c == '~';
}

bool skip_whitespace(FILE *src) {
    for (;;) {
        int c = getc(src);
        if (c < 0) {
            return false;
        }
        if (isspace(c)) {
            continue;
        }
        ungetc(c, src);
        return true;
    }
}

bool next_identifier(char *dst, FILE *src) {
    for (int i = 0; true; ++i) {
        int c = getc(src);
        if (c < 0) {
            return false;
        }
        if (isspace(c)) {
        exit:
            dst[i] = '\0';
            if (strcmp(dst, "if") == 0) {
                strcpy(dst, "__if");
            }
            return true;
        }
        if (c == ')') {
            ungetc(c, src);
            goto exit;
        }
        if (isalnum(c)) {
            dst[i] = c;
            continue;
        }
        dst[i++] = '_';
        switch (c) {
        case '\'':
            dst[i++] = 'p';
            break;
        case '?':
            dst[i++] = 'q';
            break;
        case '+':
            i += sprintf(dst + i, "add");
            break;
        case '-':
            i += sprintf(dst + i, "sub");
            break;
        case '*':
            i += sprintf(dst + i, "mul");
            break;
        case '/':
            i += sprintf(dst + i, "div");
            break;
        case '^':
            i += sprintf(dst + i, "pow");
            break;
        case '=':
            i += sprintf(dst + i, "eq");
            break;
        case '>':
            i += sprintf(dst + i, "gt");
            break;
        case '<':
            i += sprintf(dst + i, "lt");
            break;
        case '~':
            i += sprintf(dst + i, "not");
            break;
        default:
            return false;
        }
        dst[i] = '_';
    }
}

bool next_integer_literal(char *dst, FILE *src) {
    for (int i = sprintf(dst, "new BigInteger(\"%c", getc(src)); true; ++i) {
        int c = getc(src);
        if (c < 0) {
            return false;
        }
        if (isspace(c)) {
        exit:
            strcpy(dst + i, "\")");
            return true;
        }
        if (c == ')') {
            ungetc(c, src);
            goto exit;
        }
        if (isdigit(c)) {
            dst[i] = c;
            continue;
        }
        return false;
    }
}

bool next_string_literal(char *dst, FILE *src) {
    for (int i = sprintf(dst, "new String(\""); true; ++i) {
        int c = getc(src);
        if (c < 0) {
            return false;
        }
        if (c == '\\') {
            dst[i++] = c;
            c = getc(src);
            if (c < 0) {
                return false;
            }
            dst[i] = c;
            continue;
        }
        if (c == '\"') {
            strcpy(dst + i, "\")");
            return true;
        }
        dst[i] = c;
    }
}

bool next_token(char *dst, FILE *src) {
    if (!skip_whitespace(src)) {
        return false;
    }
    int c = getc(src);
    if (c < 0) {
        return false;
    }
    if (c == '(' || c == ')') {
        dst[0] = c;
        dst[1] = '\0';
        return true;
    }
    if (is_start_of_identifier(c)) {
        ungetc(c, src);
        return next_identifier(dst, src);
    }
    if (isdigit(c)) {
        ungetc(c, src);
        return next_integer_literal(dst, src);
    }
    if (c == '\"') {
        return next_string_literal(dst, src);
    }
    return false;
}

bool tokenize(GPtrArray *dst, FILE *src) {
    for (;;) {
        char s[STRLEN];
        if (!next_token(s, src)) {
            if (feof(src)) {
                break;
            }
            return false;
        }
        char *s_ = malloc(strlen(s) + 1);
        strcpy(s_, s);
        g_ptr_array_add(dst, s_);
    }
    return true;
}

bool translate(FILE *, GPtrArray *, int *);

bool translate_main(FILE *dst, GPtrArray *src, int *ip) {
    fputs("public static void main(String[] a) {\n", dst);
    for (;;) {
        if (!translate(dst, src, ip)) {
            return false;
        }
        fputs(";\n", dst);
        if (strcmp(g_ptr_array_index(src, *ip + 1), ")") == 0) {
            break;
        }
    }
    fputs("}\n", dst);
    return true;
}

bool translate_def(FILE *dst, GPtrArray *src, int *ip) {
    char *s = g_ptr_array_index(src, ++*ip);
    if (strcmp(s, "(") != 0) {
        fprintf(dst, "static Object %s = ", s);
        if (!translate(dst, src, ip)) {
            return false;
        }
        fputs(";\n", dst);
        return true;
    }
    s = g_ptr_array_index(src, ++*ip);
    fprintf(dst, "static Object %s(", s);
    bool b = false;
    for (;;) {
        s = g_ptr_array_index(src, ++*ip);
        if (strcmp(s, ")") == 0) {
            break;
        }
        if (b) {
            fputs(", ", dst);
        } else {
            b = !b;
        }
        fprintf(dst, "Object %s", s);
    }
    fputs(") {\nreturn ", dst);
    if (!translate(dst, src, ip)) {
        return false;
    }
    fputs(";\n}\n", dst);
    return true;
}

bool translate_if(FILE *dst, GPtrArray *src, int *ip) {
    fputs("__if(", dst);
    if (!translate(dst, src, ip)) {
        return false;
    }
    fputs(", new Expression() {Object eval() {return ", dst);
    if (!translate(dst, src, ip)) {
        return false;
    }
    fputs(";}}, new Expression() {Object eval() {return ", dst);
    if (!translate(dst, src, ip)) {
        return false;
    }
    fputs(";}})", dst);
    return true;
}

bool translate(FILE *dst, GPtrArray *src, int *ip) {
    if (++*ip >= src->len) {
        return false;
    }
    char *s = g_ptr_array_index(src, *ip);
#define BIG_INTEGER "new BigInteger"
#define STRING "new String"
    if (strncmp(s, BIG_INTEGER, sizeof BIG_INTEGER - 1) == 0 || strncmp(s, STRING, sizeof STRING - 1) == 0) {
        fputs(s, dst);
        return true;
    }
#undef BIG_INTEGER
#undef STRING
    if (strcmp(s, ")") == 0) {
        return true;
    }
    if (strcmp(s, "(") == 0) {
        s = g_ptr_array_index(src, ++*ip);
        if (strcmp(s, "main") == 0) {
            return translate_main(dst, src, ip);
        }
        if (strcmp(s, "def") == 0) {
            return translate_def(dst, src, ip);
        }
        if (strcmp(s, "__if") == 0) {
            return translate_if(dst, src, ip);
        }
        fprintf(dst, "%s(", s);
        bool b = false;
        for (;;) {
            if (strcmp(g_ptr_array_index(src, *ip + 1), ")") == 0) {
                ++*ip;
                break;
            }
            if (b) {
                fputs(", ", dst);
            } else {
                b = !b;
            }
            if (!translate(dst, src, ip)) {
                return false;
            }
        }
        putc(')', dst);
        return true;
    }
    fputs(s, dst);
    return true;
}

void cat(FILE *dst, FILE *src) {
    int c;
    while ((c = getc(src)) >= 0) {
        putc(c, dst);
    }
}

void clear_dir(void) {
    GDir *d = g_dir_open(".", 0, NULL);
    if (d == NULL) {
        E("clear_dir", "g_dir_open");
        abort();
    }
    for (;;) {
        const char *cp = g_dir_read_name(d);
        if (cp == NULL) {
            break;
        }
        if (cp[0] == '.') {
            continue;
        }
        if (remove(cp) != 0) {
            E("clear_dir", "remove");
            abort();
        }
    }
    g_dir_close(d);
}

void checked_chdir(const char *cp) {
    if (g_chdir(cp) < 0) {
        E("checked_chdir", "g_chdir");
        abort();
    }
}

void checked_rmdir(const char *cp) {
    if (g_rmdir(cp) < 0) {
        E("checked_rmdir", "g_rmdir");
        abort();
    }
}

int main(int argc, char **argv) {
    if (argc == 1) {
        fprintf(stdout, "error: argc == 1\n");
        exit(EXIT_FAILURE);
    }
    char dir[] = "XXXXXX";
    if (g_mkdtemp(dir) == NULL) {
        E("main", "g_mkdtemp");
        exit(EXIT_FAILURE);
    }
    char mjl[STRLEN], mjl_copy[STRLEN], java[STRLEN], jar[STRLEN], jar_copy[STRLEN];
    strcpy(mjl, argv[1]);
    strcat(mjl, ".mjl");
    strcpy(mjl_copy, dir);
    strcat(mjl_copy, "/");
    strcat(mjl_copy, mjl);
    strcpy(java, "_");
    strcat(java, argv[1]);
    strcat(java, ".java");
    strcpy(jar, argv[1]);
    strcat(jar, ".jar");
    strcpy(jar_copy, "../");
    strcat(jar_copy, jar);
    FILE *f_mjl = fopen(mjl, "r");
    if (f_mjl == NULL) {
        E("main", "fopen(mjl)");
        goto exit_before_chdir;
    }
    FILE *f_mjl_copy = fopen(mjl_copy, "w");
    if (f_mjl_copy == NULL) {
        E("main", "fopen(mjl_copy)");
        goto exit_before_chdir;
    }
    cat(f_mjl_copy, f_mjl);
    fclose(f_mjl);
    fclose(f_mjl_copy);
    checked_chdir(dir);
    f_mjl = fopen(mjl, "r");
    if (f_mjl == NULL) {
        E("main", "fopen(mjl)");
        goto exit;
    }
    FILE *f_java = fopen(java, "w");
    if (f_java == NULL) {
        E("main", "fopen(java)");
        goto exit;
    }
    fprintf(f_java, "%s", A);
    fprintf(f_java, "\nclass _%s {\n", argv[1]);
    fprintf(f_java, "%s", B);
    fprintf(f_java, "%s", C);
    fprintf(f_java, "\n/* translation start */\n\n");
    GPtrArray *pa = g_ptr_array_new_with_free_func(free);
    if (!tokenize(pa, f_mjl)) {
        E("main", "tokenize");
        goto exit;
    }
    int n = -1;
    for (;;) {
        if (!translate(f_java, pa, &n)) {
            if (n == pa->len) {
                break;
            }
            E("main", "translate");
            goto exit;
        }
    }
    g_ptr_array_free(pa, true);
    fprintf(f_java, "\n/* translation end */\n\n}\n");
    fclose(f_mjl);
    fclose(f_java);
    char *argv_[6] = {"javac", java};
    char *stdout_, *stderr_;
    if (!g_spawn_sync(NULL, argv_, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &stdout_, &stderr_, NULL, NULL)) {
        E("main", "g_spawn_sync(\"javac\")");
        goto exit;
    }
    printf("%s%s", stdout_, stderr_);
    FILE *f_m = fopen("m.txt", "w");
    if (f_m == NULL) {
        E("main", "fopen(\"m.txt\")");
        goto exit;
    }
    fprintf(f_m, "Main-Class: _%s\n", argv[1]);
    fclose(f_m);
    argv_[0] = "jar";
    argv_[1] = "cmf";
    argv_[2] = "m.txt";
    argv_[3] = jar;
    argv_[4] = "*.class";
    if (!g_spawn_sync(NULL, argv_, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &stdout_, &stderr_, NULL, NULL)) {
        E("main", "g_spawn_sync(\"jar\")");
        goto exit;
    }
    printf("%s%s", stdout_, stderr_);
    FILE *f_jar = fopen(jar, "rb");
    if (f_jar == NULL) {
        E("main", "fopen(f_jar)");
        goto exit;
    }
    FILE *f_jar_copy = fopen(jar_copy, "wb");
    if (f_jar_copy == NULL) {
        E("main", "fopen(f_jar_copy)");
        goto exit;
    }
    cat(f_jar_copy, f_jar);
    fclose(f_jar);
    fclose(f_jar_copy);
    goto exit;
exit_before_chdir:
    checked_chdir(dir);
exit:
    clear_dir();
    checked_chdir("..");
    checked_rmdir(dir);
    return EXIT_SUCCESS;
}

build.bat

bash -c "ruby a.rb && cat mjlc.c >> out.c && gcc out.c -omjlc -std=c11 -pedantic -Wall `pkg-config --cflags --libs glib-2.0` -fwhole-program -O3 -s && rm out.c"

a.rb

def f(dst, src, str)
    dst.puts("#define #{str} \\")
    src.readlines.each do |line|
        dst.puts("\"#{line.chomp.tr("\t", "")}\\n\"\\")
    end
    dst.puts()
end

in_a = File.open("a.java", "rb")
in_b = File.open("b.java", "rb")
in_c = File.open("c.java", "rb")
out = File.open("out.c", "wb")
f(out, in_a, "A")
f(out, in_b, "B")
f(out, in_c, "C")
in_a.close
in_b.close
in_c.close
out.close

a.java

import java.math.BigInteger;

class List {
    Object a;
    Object b;
}

class Expression {
    Object eval() {
        return null;
    }
}

b.java

static Object cons(Object a, Object b) {
    List c = new List();
    c.a = a;
    c.b = b;
    return c;
}

static Object list() {
    return null;
}

static Object list(Object a) {
    return cons(a, null);
}

static Object list(Object a, Object... b) {
    Object c = list(b[b.length - 1]);
    for(int i = b.length - 2; i >= 0; i--) {
        c = cons(b[i], c);
    }
    return cons(a, c);
}

static Object car(Object a) {
    if (a.getClass().equals(List.class)) {
        return ((List)a).a;
    } else {
        return null;
    }
}

static Object cdr(Object a) {
    if (a.getClass().equals(List.class)) {
        return ((List)a).b;
    } else {
        return null;
    }
}

static Object print(Object a) {
    if (a.getClass().equals(Boolean.class)) {
        System.out.print(((Boolean)a).booleanValue());
    } else if (a.getClass().equals(BigInteger.class)) {
        System.out.print(((BigInteger)a).toString());
    } else if (a.getClass().equals(String.class)) {
        System.out.print((String)a);
    } else if (a.getClass().equals(List.class)) {
        System.out.print('(');
        while (true) {
            print(car(a));
            a = cdr(a);
            if (a == null) {
                break;
            } else {
                System.out.print(' ');
            }
        }
        System.out.print(')');
    }
    return null;
}

static Object print_sub_line(Object a) {
    print(a);
    System.out.println();
    return null;
}

static Object __if(Object a, Expression b, Expression c) {
    if (a.getClass().equals(Boolean.class)) {
        if (((Boolean)a).booleanValue()) {
            return b.eval();
        } else {
            return c.eval();
        }
    } else {
        return null;
    }
}

c.java

static Object _add_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return ((BigInteger)a).add((BigInteger)b);
    } else {
        return null;
    }
}

static Object _sub_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return ((BigInteger)a).subtract((BigInteger)b);
    } else {
        return null;
    }
}

static Object _mul_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return ((BigInteger)a).multiply((BigInteger)b);
    } else {
        return null;
    }
}

static Object _div_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return ((BigInteger)a).divide((BigInteger)b);
    } else {
        return null;
    }
}

static Object _pow_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return ((BigInteger)a).pow(((BigInteger)b).intValue());
    } else {
        return null;
    }
}

static Object _eq_(Object a, Object b) {
    if (a == null && b == null) {
        return new Boolean(true);
    } else if (a == null || b == null) {
        return new Boolean(false);
    } else if (a.getClass().equals(Boolean.class) && b.getClass().equals(Boolean.class)) {
        return new Boolean(((Boolean)a).booleanValue() == ((Boolean)b).booleanValue());
    } else if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return new Boolean(((BigInteger)a).equals((BigInteger)b));
    } else if (a.getClass().equals(String.class) && b.getClass().equals(String.class)) {
        return new Boolean(((String)a).equals((String)b));
    } else {
        return null;
    }
}

static Object _gt_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return new Boolean(((BigInteger)a).compareTo((BigInteger)b) > 0);
    } else {
        return null;
    }
}

static Object _lt_(Object a, Object b) {
    if (a.getClass().equals(BigInteger.class) && b.getClass().equals(BigInteger.class)) {
        return new Boolean(((BigInteger)a).compareTo((BigInteger)b) < 0);
    } else {
        return null;
    }
}

static Object _not_(Object a) {
    if (a.getClass().equals(Boolean.class)) {
        return new Boolean(!((Boolean)a).booleanValue());
    } else {
        return null;
    }
}
\$\endgroup\$
5
\$\begingroup\$
return isalpha(c) || c == '\'' || c == '?' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == '=' || c == '>' || c == '<' || c == '~';

This could be rewritten much more readably as

return isalpha(c) || (c != '\0' && strchr("'?+-*/^=><~", c) != NULL);

A good compiler will produce the same code for both. (Neither GCC nor Clang count as good compilers. ;) However, they both produce a call to __ctype_b_loc for isalpha, which is pretty bad in itself.)


When possible, avoid non-local control flow such as continue and goto. For example, I'd rewrite your skip_whitespace as simply:

bool skip_whitespace(FILE *src)
{
    int c;
    while ((c = getc(src)) != EOF) {
        if (!isspace(c)) {
            ungetc(c, src);
            return true;
        }
    }
    return false;
}

The next_identifier function also suffers from convoluted control flow. Particularly, that strcmp(dst, "if") is a code smell. Why are you checking only for the name if? What happens if the MJLisp programmer makes a function named while — does your code still work, or does it try to create a Java variable named while and blow up?

I would strongly recommend using a properly round-trippable name-mangling/encoding scheme. For example, you could use a slightly modified form of URL-encoding: each alphabetic character encodes to itself, and each punctuation character (including _) encodes to sprintf(dst, "_%02X", c). To make sure we don't collide with any Java keywords, I'd prefix each identifier with mjl. So for example the MJLisp name if would encode to mjlif, and i++ would encode to mjli_2B_2B.

Your current code also has the problem (bug?) that names containing _ are not encodable at all.


Function next_integer_literal has the problem that it treats c == ')' as a special case. I'm pretty sure you should be using that codepath for any non-digit character. And then rewrite to avoid non-local control flow:

bool next_integer_literal(char *dst, FILE *src)
{
    int i = sprintf(dst, "new BigInteger(\"");
    int c;
    while ((c = getc(src)) != EOF) {
        if (isdigit(c)) {
            dst[i++] = c;
        } else {
            ungetc(c, src);
            sprintf(dst + i, "\")");
            return true;
        }
    }
    return false;
}

Incidentally, notice how I replaced your strcpy with sprintf, there? That's so that it'll be really easy to go back later and replace all your sprintfs with snprintf. Right now, this entire program is a buffer overflow waiting to happen. It is absolutely imperative that you rewrite this whole thing with buffer lengths in mind. For example:

char *next_integer_literal(char *dst, const char *end, FILE *src)
{
    dst += snprintf(dst, end - dst, "new BigInteger(\"");
    int c;
    while ((c = getc(src)) != EOF) {
        if (isdigit(c)) {
            dst += snprintf(dst, end - dst, "%c", c);
        } else {
            ungetc(c, src);
            dst += snprintf(dst, end - dst, "\")");
            return dst;
        }
    }
    return NULL;
}

Notice that if we get all the way back up to the top level and find that dst == end, we know that we've hit the end of our buffer and therefore we can't trust the result.


    char *s_ = malloc(strlen(s) + 1);
    strcpy(s_, s);
    g_ptr_array_add(dst, s_);

You fail to check for NULL returned from malloc, which is par for the course in C, so I wouldn't worry about it unless you're planning to use this code in mission-critical settings. However, you could rewrite this as a one-liner by either using a glibc extension or implementing it yourself if you're on a non-glibc platform:

    g_ptr_array_add(dst, strdup(s));

You use the name dst for a char buffer, a FILE *, and a GPtrArray * at different points in the program. This is a bit jarring when skimming the code in a small browser window — "Wait, you can't fputs into a char pointer! ...oh, I see, it's a FILE here" —; it might be totally fine in your editor of choice.


Your main is a huge mess. First of all, you seem to have forgotten about the existence of sprintf; secondly, you should encapsulate each stage of the "compilation pipeline" into its own named function. For example,

int main(int argc, char **argv)
{
    if (argc == 1) {
        fprintf(stderr, "Usage: ...\n");
        exit(EXIT_FAILURE);
    }
    char mjl[1000], mjl_copy[1000], java[1000], jar[1000], mtxt[1000], jar_copy[1000];
    snprintf(mjl, sizeof mjl, "%s.mjl", argv[1]);
    snprintf(mjl_copy, sizeof mjl_copy, "/tmp/%s.mjl", argv[1]);
    snprintf(java, sizeof java, "/tmp/_%s.java", argv[1]);
    snprintf(jar, sizeof jar, "/tmp/%s.jar", argv[1]);
    snprintf(mtxt, sizeof mtxt, "/tmp/m.txt");
    snprintf(jar_copy, sizeof jar_copy, "../%s.jar", argv[1]);
    copy_file(mjl, mjl_copy);
    compile_mjl_to_java(mjl_copy, java);
    compile_java_to_jar(java, mtxt, jar);
    copy_file(jar, jar_copy);  // why?
    remove_file(mjl_copy);
    remove_file(java);
    remove_file(mtxt);
    remove_file(jar);
}

The next step in refactoring would be to notice that you don't need copy_file at all; you don't modify the copy, so the copy is exactly the same as the original file, so you don't need to make the copy in the first place. The same goes for copying the jar file; you can just tell jar cmf to create the jar file in its final location, instead of creating it in /tmp and then moving it to the right place later.

(Also, in general, notice that using the C library function rename, which has the semantics of mv, will be faster and all-around better than copying the physical bytes around.)

Getting rid of those copies will shrink your main routine by about 50%, and mean you can get rid of your cat function.


You can (and should) also get rid of your checked_chdir function. Programs that change their own working directory while they're running are the devil. *cough*  Just use absolute paths for everything; your code will be easier to reason about, and your users will thank you.

(Think of the current working directory, and the OS environment in general, as one big global variable. Try really really hard not to use that global variable to communicate between functions that are otherwise well-behaved. Part of not-using global state is not-modifying it.)

\$\endgroup\$
  • 1
    \$\begingroup\$ "Neither GCC nor Clang count as good compilers." Could you add what in your opinion counts as a good compiler to make this statement complete? Your current statement is open for interpretation and might invoke a holy war along the way. \$\endgroup\$ – Mast Feb 9 '16 at 8:20
  • \$\begingroup\$ I think we can agree that a good compiler would produce the same code for both snippets. Empirically, GCC and Clang don't (yet) produce the same code for both snippets. Therefore, they must not be good compilers. :) \$\endgroup\$ – Quuxplusone Feb 9 '16 at 8:25
0
\$\begingroup\$

Quuxplusone has already provided a very good answer. A few minor additions:

  1. I don't quite see the need for strcmp here:

    if (strcmp(g_ptr_array_index(src, *ip + 1), ")") == 0) {
    

    A character comparison should suffice:

    if (*g_ptr_array_index(src, *ip + 1) == ')') {
    
  2. In generally parsing programming languages is a pretty much solved problem. If you really want to develop this project much further you should seriously look into parser generators like Bison instead of hand-rolling your own.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.