with-c-syntax
with-c-syntax copied to clipboard
C language syntax in Common Lisp
-- mode: org; coding: utf-8; --
- 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][Statement Expression]] and [[#support-with--like-macros][with- like syntax support]] were added.
- (2021-9-5) C Preprocessor is added. See [[#c-preprocessor][C preprocessor examples]].
- (2021-5-24) C Numeric Literals are added. See [[#inline-usage][examples in 'inline usage' section]]. (Inspired by [[https://github.com/akanouras][@akanouras]] at [[https://github.com/y2q-actionman/with-c-syntax/pull/7][PR #7]].)
- (2019-4-25) Some special handlings around =++=, =*=, etc are added. See [[#duffs-device][Duff's Device example]] .
- (2019-4-25) Added a new example, [[#c-in-lisp-in-c-in-lisp][C in Lisp in C in Lisp]].
- Examples ** Hello, World
#+BEGIN_SRC lisp CL-USER> (with-c-syntax:with-c-syntax () format ( t , "Hello World!" ) ; )
Hello World! NIL #+END_SRC
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:
#+BEGIN_SRC lisp ;; 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 #+END_SRC
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.
#+begin_src lisp (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
#+end_src
Because this macro supports C numeric literals, Using hexadecimal floating number syntax may be a only practical feature of this package.
#+begin_src lisp (named-readtables:in-readtable with-c-syntax:with-c-syntax-readtable)
(princ #{ 0x1.fffp+1 }#)
;; => 3.99951171875d0
#+end_src
** Summing from 1 to 100.
#+BEGIN_SRC lisp (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 #+END_SRC
** Using C syntax inside a Lisp function.
#+BEGIN_SRC lisp (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)) #+END_SRC
** Defining a function with C syntax.
#+BEGIN_SRC lisp (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 #+END_SRC
** Duff's Device #+BEGIN_SRC lisp (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) #+END_SRC
This example shows some C operators (=++=, =--=, unary =*= and =&=) behave as you expected as possible.
(This feature is based on [[https://github.com/phoe][@phoe]]'s suggestion. See [[https://github.com/y2q-actionman/with-c-syntax/issues/2][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:
Let's see the unholy mixture..
#+BEGIN_SRC lisp (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 #+END_SRC
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.
TODO: add a link.
** Syntax extensions
*** Statement Expression
You can treat any statements as a expression by surrounding =(= and =)=. This is derived from [[https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html][GCC]].
#+begin_src common-lisp #{ int z = ({ int x = 1, y = 2; return x + y; }); return z; }# ; => 3 #+end_src
*** Support ~with-~ like macros.
(You can use any Lisp operators including =with-open-file= in =with-c-syntax= style.
However it looks very weird; [[https://github.com/y2q-actionman/with-c-syntax/blob/e3e9ae2f1f29115f30141e3ada33372e2ce6b65d/test/libc_string.lisp#L143][An example exists in my test code]].)
=with-c-syntax= has a syntax extensiton for ~with-~ like macros:
#+begin_example identifier lisp-expression statement; #+end_example>
This is compiled to a Lisp form like below:
#+begin_example
(identifier (
(This feature is based on [[https://github.com/phoe][@phoe]]'s suggestion. See [[https://github.com/y2q-actionman/with-c-syntax/issues/4][Issue #4]] .)
Here are some examples:
**** =with-slots=
#+begin_src common-lisp (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 #+end_src
**** =with-output-to-string= and statement expression
You can take the value of =with-= syntax statement by wrapping it with =()=.
#+begin_src common-lisp (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!" #+end_src
**** 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:
#+begin_src common-lisp (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) #+end_src
** C Preprocessor
*** C Macros
=#define= can be used. This is a well-known MAX macro example.
#+begin_src lisp (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 #+end_src
But you know Common Lisp already has [[http://www.lispworks.com/documentation/HyperSpec/Body/f_max_m.htm][CL:MAX]]. We can use it directly:
#+begin_src lisp (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 #+end_src
=#= (stringify) and =##= (concatenate) operator can be used, but only in Level 2 syntax (because it conflicts with standard Lisp '#' syntax.)
#+begin_src lisp (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)) }#) #+end_src
(Yes, you can use these transformation more freely in Lisp macro!)
*** Conditional Inclusion
=#if= family is supported. Simple example:
#+begin_src lisp (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 #+end_src
=#if= also works as expected. It can evaluate any Lisp expressions using =`= syntax. This feature enables to use =features= by =#if= conditionals:
#+begin_src lisp (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" #+end_src
*** =#include=
=#include= works as you know:
#+begin_src lisp (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 #+end_src
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].
#+begin_src lisp (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 #+end_src
(But in the Lisp world, you already have =read=, =eval=, and =load=...)
- How to load
** Loading by quicklisp
This library is quicklisp-ready on [[http://blog.quicklisp.org/2021/08/august-2021-quicklisp-dist-update-now.html][August 2021 dist]].
#+BEGIN_SRC lisp (ql:quickload "with-c-syntax") #+END_SRC
** 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 test =NDEBUG=.
- floating-point :: For comparing mathmatical function results.
*** Load with ASDF #+BEGIN_SRC lisp (asdf:load-asd "with-c-syntax.asd") (asdf:load-system :with-c-syntax) #+END_SRC
*** Running tests #+BEGIN_SRC lisp (asdf:load-asd "with-c-syntax-test.asd") (asdf:test-system :with-c-syntax) #+END_SRC
*** CI
[[https://github.com/y2q-actionman/with-c-syntax/actions/workflows/linux-sbcl-testSystem.yml/badge.svg]]
[[https://github.com/y2q-actionman/with-c-syntax/actions/workflows/linux-load.yml/badge.svg]]
[[https://github.com/y2q-actionman/with-c-syntax/actions/workflows/macos-load.yml/badge.svg]]
There are Github Actions to run the test above.
I wrote current recipes referring the example of [[https://github.com/neil-lindquist/CI-Utils][CI-Utils]].
- API Please see these docstrings or comments:
- Macro [[https://github.com/y2q-actionman/with-c-syntax/blob/95eebdc79eb8dc8c5c3e29d218e447b3ff2b949c/src/with-c-syntax.lisp#L15-L46][with-c-syntax]]
- Comments around [[https://github.com/y2q-actionman/with-c-syntax/blob/95eebdc79eb8dc8c5c3e29d218e447b3ff2b949c/src/reader.lisp#L792-L820][with-c-syntax-readtable]]
- Variable [[https://github.com/y2q-actionman/with-c-syntax/blob/95eebdc79eb8dc8c5c3e29d218e447b3ff2b949c/src/reader.lisp#L5-L100][with-c-syntax-reader-level]]
- Variable [[https://github.com/y2q-actionman/with-c-syntax/blob/95eebdc79eb8dc8c5c3e29d218e447b3ff2b949c/src/reader.lisp#L102-L111][with-c-syntax-reader-case]]
- Variable [[https://github.com/y2q-actionman/with-c-syntax/blob/95eebdc79eb8dc8c5c3e29d218e447b3ff2b949c/src/reader.lisp#L113-L115][previous-readtable]]
- Variable [[https://github.com/y2q-actionman/with-c-syntax/blob/95eebdc79eb8dc8c5c3e29d218e447b3ff2b949c/src/preprocessor.lisp#L19-L23][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
[[https://github.com/vsedach/Vacietis][Vacietis]] is a similer project. It is a "C to Common Lisp" compiler, based on reader macros.
"[[https://evilmartians.com/chronicles/a-no-go-fantasy-writing-go-in-ruby-with-ruby-next][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.