https://github.com/y2q-actionman/with-c-syntax
C language syntax in Common Lisp
https://github.com/y2q-actionman/with-c-syntax
Last synced: 3 months ago
JSON representation
C language syntax in Common Lisp
- Host: GitHub
- URL: https://github.com/y2q-actionman/with-c-syntax
- Owner: y2q-actionman
- License: wtfpl
- Created: 2014-07-24T05:01:53.000Z (over 11 years ago)
- Default Branch: master
- Last Pushed: 2022-11-08T06:03:33.000Z (over 3 years ago)
- Last Synced: 2025-03-20T14:41:05.416Z (about 1 year ago)
- Language: Common Lisp
- Homepage:
- Size: 1.01 MB
- Stars: 149
- Watchers: 6
- Forks: 7
- Open Issues: 3
-
Metadata Files:
- Readme: README.org
- License: COPYING
Awesome Lists containing this project
- awesome-cl - with-c-syntax - a fun package which introduces the C language syntax into Common Lisp. (Yes, this package is not for practical coding, I think.) WTFPL Licence. (Expert Systems)
README
# -*- 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 ( ...)
...)
#+end_example
(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
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.