with-c-syntax

2022-11-07

with-c-syntax is a fun package which introduces the C language syntax into Common Lisp.

Upstream URL

github.com/y2q-actionman/with-c-syntax

Author

YOKOTA Yuki <y2q.actionman@gmail.com>

License

WTFPL
README

1Abstract

with-c-syntax is a fun package which introduces the C languagesyntax into Common Lisp. (Yes, this package is not for practicalcoding, I think.)

At this stage, this package has all features of ISO C 90 freestanding implementation.

2News

3Examples

3.1Hello, 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.

3.2Inline 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

3.3Summing 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

3.4Using 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))

3.5Defining 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

3.6Duff'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 .)

3.7C 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.

3.8Syntax extensions

3.8.1Statement 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

3.8.2Support with- like macros.

with-c-syntax has a syntax extensiton for with- like macros:

identifier lisp-expression statement;
#+end_example>

This is compiled to a Lisp form like below:

#+begin_example
(identifier (<contents in lisp-expression> ...)
  <contents in statement>
  ...)

(This feature is based on @phoe's suggestion. See Issue #4 .)

Here are some examples:

3.8.2.1with-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

3.8.2.2with-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!"

3.8.2.3Using 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)

3.9C Preprocessor

3.9.1C 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!)

3.9.2Conditional 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"

3.9.3#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...)

4How to load

4.1Loading by quicklisp

This library is quicklisp-ready on August 2021 dist.

(ql:quickload "with-c-syntax")

4.2or, Loading manually

4.2.1Libraries 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 <...>

4.2.1.1by libc

float-features
For math.h, dealing NaN and Infinities.
floating-point-contractions
For math.h, to implement some functions.

4.2.1.2by test codes

1am
As a testing framework.
trivial-cltl2
For using compiler-let to test NDEBUG.
floating-point
For comparing mathmatical function results.

4.2.2Load with ASDF

(asdf:load-asd "with-c-syntax.asd")
(asdf:load-system :with-c-syntax)

4.2.3Running tests

(asdf:load-asd "with-c-syntax-test.asd")
(asdf:test-system :with-c-syntax)

4.2.4CI

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 CI-Utils.

5API

Please see these docstrings or comments:

6Further 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.

7License

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.

8Footnotes

[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.

Dependencies (11)

  • 1am
  • alexandria
  • cl-ppcre
  • cl-yacc
  • float-features
  • floating-point
  • floating-point-contractions
  • named-readtables
  • split-sequence
  • trivial-cltl2
  • trivial-gray-streams

Dependents (0)

    • GitHub
    • Quicklisp