Earlier quoted context omitted.
I quite intentionally said "within the language" to preempt nonsense like "Of course, you can. C is perfectly capable of writing C interpreters and compilers."
What does 'within the language' mean? Is the standard library part of the language? Would a variant of C that came with an interpreter in the standard library (but no other changes) count as homoiconic?
#include
int main(void) {
/* 1. Symbols: interned, identity-comparable. */
symbol x = #x;
symbol x2 = intern("x");
assert(x == x2); // same identity, not just strcmp
assert(x != #y);
printf("symbol name: %s\n", symbol_name(x)); // -> "x"
/* 2. Code is written in *the same syntax* as code that runs.
* `code{ 1 + 2 }` is a value of type `code` whose printed
* form is literally "1 + 2". No separate DSL. */
code expr = code{ 1 + 2 };
printf("as source: %s\n", code_to_string(expr)); // -> "1 + 2"
printf("evaluates: %ld\n", (long)eval(expr)); // -> 3
/* 3. Build the same AST programmatically; it is structurally
* equal to the literal above. */
code built = code_add(code_int(1), code_int(2));
assert(code_equal(expr, built));
/* 4. Quasiquote: a code template with a hole, written in C syntax.
* `~n` splices the runtime value of n into the form. */
long n = 40;
code tpl = code{ ~n + (1 + 1) };
printf("template: %s\n", code_to_string(tpl)); // -> "40 + (1 + 1)"
printf("evaluates: %ld\n", (long)eval(tpl)); // -> 42
/* 5. A program can rewrite its own code, because code is data.
* Double every integer literal in a form. */
code e2 = code{ (1 + 1) + 2 };
code doubled = map_code(e2, double_int_literals);
printf("rewritten: %s = %ld\n",
code_to_string(doubled), (long)eval(doubled));
// -> "(2 + 2) + 4 = 8"
/* 6. Definitions are code too. Write the definition in C syntax,
* then eval the code value to install it at runtime. */
code square_def = code{
long square(long x) { return x * x; }
};
eval(square_def);
printf("square(7) = %ld\n", (long)eval(code{ square(7) })); // -> 49
/* 7. Macros: compile-time functions from code to code, written
* in the same syntax they transform. */
code swap_macro = code{
macro swap(a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
};
eval(swap_macro); // install the macro
eval(code{ long u = 1; });
eval(code{ long v = 2; });
eval(code{ swap(u, v); }); // macro expands, then runs
printf("after swap: u=%ld v=%ld\n",
(long)eval(code{ u }), (long)eval(code{ v })); // -> u=2 v=1
return 0;
}
Here is the code translated directly to S-expression syntax. This is not exactly conventional Lisp in terms of how variables are defined (explicitly typed instead of inferred is a bit weird for Lisp), but I wanted it to be as close as possible to see the parallels. (include )
(defun int main ((void))
;; 1. Symbols: interned, identity-comparable.
(symbol x #x)
(symbol x2 (intern "x"))
(assert (== x x2)) ; same identity, not just strcmp
(assert (!= x #y))
(printf "symbol name: %s\n" (symbol_name x)) ; -> "x"
;; 2. Code is written in the *same syntax* as code that runs.
;; Now that the whole language is uniform, a plain quote is all
;; it takes: '(+ 1 2) is a `code` value that prints back as
;; "(+ 1 2)".
(code expr '(+ 1 2))
(printf "as source: %s\n" (code_to_string expr)) ; -> "(+ 1 2)"
(printf "evaluates: %ld\n" (long (eval expr))) ; -> 3
;; 3. Build the same AST programmatically; structurally equal.
(code built (code_add (code_int 1) (code_int 2)))
(assert (code_equal expr built))
;; 4. Quasiquote: a template with a hole. ,n splices n's value.
(long n 40)
(code tpl `(+ ,n (+ 1 1)))
(printf "template: %s\n" (code_to_string tpl)) ; -> "(+ 40 (+ 1 1))"
(printf "evaluates: %ld\n" (long (eval tpl))) ; -> 42
;; 5. A program can rewrite its own code. Double every int literal.
(code e2 '(+ (+ 1 1) 2))
(code doubled (map_code e2 double_int_literals))
(printf "rewritten: %s = %ld\n"
(code_to_string doubled) (long (eval doubled)))
; -> "(+ (+ 2 2) 4) = 8"
;; 6. Definitions are code too.
(code square_def '(defun long square ((long x)) (* x x)))
(eval square_def)
(printf "square(7) = %ld\n" (long (eval '(square 7)))) ; -> 49
;; 7. Macros: compile-time code -> code, written in the same
;; syntax they transform.
(code swap_macro '(defmacro swap (a b)
(set a (^ a b))
(set b (^ a b))
(set a (^ a b))))
(eval swap_macro) ; install the macro
(eval '(long u 1))
(eval '(long v 2))
(eval '(swap u v)) ; macroexpands, then runs
(printf "after swap: u=%ld v=%ld\n"
(long (eval 'u)) (long (eval 'v))) ; -> u=2 v=1
(return 0))