Abstract
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.
News
- (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.
Examples
Hello, World
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.
Inline usage.
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
Summing from 1 to 100.
(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
Using C syntax inside a Lisp function.
(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))
Defining a function with C syntax.
(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
Duff’s Device
(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 .)
C in Lisp in C in Lisp
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.
Syntax extensions
Statement Expression
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-
like macros.
Support 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:
with-slots
(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
with-output-to-string
and statement expression
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!"
Using with an operator takes a function
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)
C Preprocessor
C Macros
#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!)
Conditional Inclusion
#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
#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
…)
How to load
Loading by quicklisp
This library is quicklisp-ready on August 2021 dist.
(ql:quickload "with-c-syntax")
or, Loading manually
Libraries depending on
- 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 <...>
by libc
- float-features
- For math.h, dealing NaN and Infinities.
- floating-point-contractions
- For math.h, to implement some functions.
by test codes
- 1am
- As a testing framework.
- trivial-cltl2
- For using
compiler-let
to testNDEBUG
. - floating-point
- For comparing mathmatical function results.
Load with ASDF
(asdf:load-asd "with-c-syntax.asd")
(asdf:load-system :with-c-syntax)
Running tests
(asdf:load-asd "with-c-syntax-test.asd")
(asdf:test-system :with-c-syntax)
CI
There are Github Actions to run the test above. I wrote current recipes referring the example of CI-Utils.
API
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*
Further Information
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.
License
Copyright (c) 2014,2019,2021 YOKOTA Yuki <[email protected]>
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.
Footnotes
[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.