with-c-syntax is a fun package which introduces the C language syntax into Common Lisp. (Yes, this package is not for practical coding, I think.)
At this stage, this package has all features of ISO C 90 freestanding implementation.
- (2022-10-9) New extensions, Statement Expression and with- like syntax support were added.
- (2021-9-5) C Preprocessor is added. See C preprocessor examples.
- (2021-5-24) C Numeric Literals are added. See examples in ‘inline usage’ section. (Inspired by @akanouras at PR #7.)
- (2019-4-25) Some special handlings around
++
,*
, etc are added. See Duff’s Device example . - (2019-4-25) Added a new example, C in Lisp in C in Lisp.
CL-USER> (with-c-syntax:with-c-syntax ()
format \( t \, "Hello World!" \) \;
)
Hello World!
NIL
For suppressing Lisp’s syntax, you need many backslash escapes.
#{
and }#
reader macro escapes them and wrap its contents
into with-c-syntax
. You can use it to write simply:
;; enables #{ }# reader macros.
CL-USER> (named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
...
CL-USER> #{ format (t, "Hello World!"); }#
Hello World!
NIL
This example shows you can call a Lisp function (cl:format
) with C syntax.
This macro can be used like a normal lisp expression. You can use it whenever C-like syntax is wanted.
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(assert (= 100 #{ 98 - 76 + 54 + 3 + 21 }#)) ; => T
;;; Reader macro parameter '2' means to split C operators even inside Lisp symbols.
(assert #2{ 1+2+3-4+5+6+78+9 == 100 }#) ; => T
Because this macro supports C numeric literals, Using hexadecimal floating number syntax may be a only practical feature of this package.
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(princ #{ 0x1.fffp+1 }#)
;; => 3.99951171875d0
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
int i, sum = 0;
for (i = 0; i <= 100; ++i)
sum += i;
return sum;
}#
;; => 5050
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(defun array-transpose (arr)
(destructuring-bind (i-max j-max) (array-dimensions arr)
#{
int i,j;
for (i = 0; i < i-max; i++) {
for (j = i + 1; j < j-max; j++) {
rotatef(arr[i][j], arr[j][i]);
}
}
}#)
arr)
(array-transpose (make-array '(3 3)
:initial-contents '((0 1 2) (3 4 5) (6 7 8))))
; => #2A((0 3 6) (1 4 7) (2 5 8))
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
int sum-of-list (list) {
int list-length = length(list);
int i, ret = 0;
for (i = 0; i < list-length; ++i) {
ret += nth(i, list);
}
return ret;
}
}#
(sum-of-list '(1 2 3 4 5 6 7 8 9 10)) ; => 55
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(defun wcs-duff-device (to-seq from-seq cnt)
#{
int *to = &to-seq;
int *from = &from-seq;
int n = floor ((cnt + 7) / 8); /* Use floor(), because Lisp's '/' produces rational */
switch (cnt % 8) {
case 0 : do { *to++ = *from++;
case 7 : *to++ = *from++;
case 6 : *to++ = *from++;
case 5 : *to++ = *from++;
case 4 : *to++ = *from++;
case 3 : *to++ = *from++;
case 2 : *to++ = *from++;
case 1 : *to++ = *from++;
} while (--n > 0);
}
}#
to-seq)
(defparameter *array-1*
(make-array 20 :initial-element 1))
;; C syntax can also be used for defining a variable.
#{
int *array-2* [] = {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
}#
(wcs-duff-device *array-1* *array-2* 10)
(print *array-1*) ;; => #(2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1)
This example shows some C operators (++
, --
, unary *
and &
)
behave as you expected as possible.
(This feature is based on @phoe’s suggestion. See Issue #2 .)
Sometimes you want to use the Lisp syntax even in with-c-syntax
.
If you feel so, you can use `
as an escape. Here is an example:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
void 99-bottles-of-beer (filename) {
void * output-path = merge-pathnames (filename, user-homedir-pathname());
`(with-open-file (*standard-output* output-path :direction :output
:if-exists :supersede :if-does-not-exist :create)
#{
int b;
for (b = 99; b >= 0; b--) {
switch (b) {
case 0 :
write-line("No more bottles of beer on the wall, no more bottles of beer.");
write-line("Go to the store and buy some more, 99 bottles of beer on the wall.");
break;
case 1 :
write-line("1 bottle of beer on the wall, 1 bottle of beer.");
write-line("Take one down and pass it around, no more bottles of beer on the wall.");
break;
default :
format(t, "~D bottles of beer on the wall, ~D bottles of beer.~%", b, b);
format(t, "Take one down and pass it around, ~D ~A of beer on the wall.~%"
, b - 1
, ((b - 1) > 1)? "bottles" : "bottle");
break;
}
}
}#);
return;
}
}#
(99-bottles-of-beer "99_bottles_of_beer.txt")
(probe-file "~/99_bottles_of_beer.txt") ; => T
This example creates “99_bottles_of_beer.txt” file into your home directory.
I used `
for using with-open-file
in Lisp syntax.
Recently, I added a syntax extension for these with-
like macros. See below.
You can treat any statements as a expression by surrounding (
and )
.
This is derived from GCC.
#{
int z = ({
int x = 1, y = 2;
return x + y;
});
return z;
}# ; => 3
with-c-syntax
has a syntax extensiton for with-
like macros:
identifier lisp-expression statement;
This is compiled to a Lisp form like below:
(identifier (<contents in lisp-expression> ...) <contents in statement> ...)
(This feature is based on @phoe’s suggestion. See Issue #4 .)
Here are some examples:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(defclass foo ()
((slot1 :initform 1)
(slot2 :initform 2)))
#{
int test-with-slots (void) {
auto obj = make-instance (`'foo);
with-slots `((slot1 slot2) obj) {
return slot1 + slot2 ;
}
}
}#
(test-with-slots) ; => 3
You can take the value of with-
syntax statement by wrapping it with ()
.
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
char * hello-world-string (void) {
return (with-output-to-string `((*standard-output*))
{
princ("Hello, World!");
});
}
}#
(hello-world-string) ; => "Hello, World!"
This syntax can currently apply to functions, not only macros. It may be useful when the function takes a function at the last argument:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
sort-ascending (lis) {
return (sort `(lis) `(lambda (x y)
#{
return x < y;
}#);
);
}
}#
(sort-ascending (list 2 4 1 5 3)) ; => (1 2 3 4 5)
#define
can be used. This is a well-known MAX macro example.
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
#define MY_MAX(x, y) ((x)>(y) ? (x) : (y))
int my-max-test (x, y) {
return MY_MAX (x, y);
}
}#
(my-max-test -1 1) ; => 1
But you know Common Lisp already has CL:MAX. We can use it directly:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
#define MY_CL_MAX(x, ...) cl:max(x, __VA_ARGS__)
int my-cl-max-test (x, y, z) {
return MY_CL_MAX (x, y, z);
}
}#
(my-cl-max-test -1 9999 0) ; => 1
#
(stringify) and ##
(concatenate) operator can be used, but
only in Level 2 syntax (because it conflicts with standard Lisp
‘#’ syntax.)
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(string=
"1.2"
#2{
#define STR(x) #x
#define EXPAND_STR(x) STR(x)
#define CAT(x,y) x##y
EXPAND_STR(CAT(1,.2))
}#)
(Yes, you can use these transformation more freely in Lisp macro!)
#if
family is supported. Simple example:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
#{
#define TEST_MACRO_DEFINITION
void * test-macro-defined-p (void) {
#ifdef TEST_MACRO_DEFINITION
return t;
#else
return nil;
#endif
}
}#
(test-macro-defined-p) ; => t
#if
also works as expected. It can evaluate any Lisp expressions
using `
syntax. This feature enables to use *features*
by
#if
conditionals:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(defun see-features-example ()
#{
#if `(member :sbcl *features* :test 'eq)
format(nil, "I am SBCL: ~A", lisp-implementation-version());
#elif `(member :allegro *features* :test 'eq)
format(nil, "I am ALLEGRO: ~A", lisp-implementation-version());
#else
"Under implementation";
#endif
}#)
(see-features-example)
;; On SBCL
;; => "I am SBCL: 2.1.7"
;; On Allegro
;; => "I am ALLEGRO: 10.1 [64-bit Mac OS X (Intel) *SMP*] (Jul 6, 2018 18:44)"
;; On other implementations
;; => "Under implementation"
#include
works as you know:
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(with-open-file (stream "/tmp/tmp.h" :direction :output :if-exists :supersede)
(format stream "const int foo = 100;"))
(defun return-foo ()
#{
#include "/tmp/tmp.h"
return foo;
}#)
(return-foo) ; => 100
When using #include
, it can be a problem which package the
symbol is interned in. It can be changed with the with-c-syntax
specific pragma [fn:1].
(named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(with-open-file (stream "/tmp/tmp.h" :direction :output :if-exists :supersede)
;; _Pragma() can be embedded in the included file.
(format stream "const int bar = 123;"))
(defpackage temp-package
(:use :cl)
(:export #:bar))
#2{
_Pragma("WITH_C_SYNTAX IN_PACKAGE \"TEMP-PACKAGE\"")
#include "/tmp/tmp.h"
}#
temp-package:bar ; => 123
(But in the Lisp world, you already have read
, eval
, and load
…)
This library is quicklisp-ready on August 2021 dist.
(ql:quickload "with-c-syntax")
- cl-yacc
- As a parser for C syntax.
- alexandria
- Many utilities.
- named-readtables
- For exporting ‘#{’ reader syntax.
- cl-ppcre
- For parsing numeric constants.
- trivial-gray-streams
- For implementing translation phase 1 and 2 correctly.
- asdf
- For using system-relative pathname, implementing
#include <...>
- float-features
- For math.h, dealing NaN and Infinities.
- floating-point-contractions
- For math.h, to implement some functions.
- 1am
- As a testing framework.
- trivial-cltl2
- For using
compiler-let
to testNDEBUG
. - floating-point
- For comparing mathmatical function results.
(asdf:load-asd "with-c-syntax.asd")
(asdf:load-system :with-c-syntax)
(asdf:load-asd "with-c-syntax-test.asd")
(asdf:test-system :with-c-syntax)
There are Github Actions to run the test above. I wrote current recipes referring the example of CI-Utils.
Please see these docstrings or comments:
- Macro with-c-syntax
- Comments around with-c-syntax-readtable
- Variable *with-c-syntax-reader-level*
- Variable *with-c-syntax-reader-case*
- Variable *previous-readtable*
- Variable *with-c-syntax-find-include-file-function-list*
What this macro does is only expanding a list of symbols to a Lisp form.
If you are still interested, please see: https://github.com/y2q-actionman/with-c-syntax/wiki
Vacietis is a similer project. It is a “C to Common Lisp” compiler, based on reader macros.
“A no-go fantasy: writing Go in Ruby with Ruby Next” takes a similer approach in Ruby.
Copyright (c) 2014,2019,2021 YOKOTA Yuki <y2q-actionman@users.noreply.github.com>
This program is free software. It comes without any warranty, to the extent permitted by applicable law. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the COPYING file for more details.
[fn:1] In this example, I used _Pragma()
operator instead of ‘#pragma’ notation because #p
is
already used by the standard syntax. Level 2 syntax only supports
that. See *with-c-syntax-reader-case*
docstring for reader levels.