Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


  • a simpler c syntax. c written as scheme-like s-expressions
  • scheme/lisp like macro system
  • supports c and its preprocessor
  • the output, after formatting, is supposed to be as if originally written in c
  • command-line application and guile library
  • possibly also useful as an intermediate language for applications that want to generate c code
  • status: should work, been around for a while, easy to maintain and extend
  • license: gpl3+. does not apply to generated code. generated code has your license
  • you can try it out here

syntax highlights

see the syntax reference further below for all features


c supports declarations for variables, arrays, structs, types and enums and they all have a different syntax. sc has declare

  a uint32_t
  b (array uint8_t 3)
  c (struct (id int) (name char*))
  d (enum (x y z))
  e (type uint16_t)
  f (type (struct (id int) (name char*))))

preprocessor macros

preprocessor macros are defined similar to variables and functions and dont need escaped newlines or special formatting

(pre-define is-included #t)
(pre-define (mymacro a b)
      a 1
      b 2)))

multiple macros can be defined at once

  is-included #t
  id-size 4
  (mymacro a b) (set a 1 b 2))


(define (myfunction a b) (int char void)
  "a description of this function"
  (return 1))

structs, addresses, pointers, types

  • structs: or (struct-get aa bb)
  • struct pointers: aa:bb:cc or (: aa bb cc)
  • addresses: &aa or (address-of aa)
  • pointers: *aa or (pointer-get aa)
  • types: (convert-type aa uint8_t)

in sc, prefixes apply consistently to the whole following expression. for example, * means *(

function pointers

function pointer syntax is

(function-pointer output-type input-types ...)

in declarations, this syntax can be used in place of type names - instead of wrapping the identifier like with c. an int variable would be declared like this

(declare b int)

a pointer to a function that takes one char argument and returns an int

(declare b (function-pointer int char))

a pointer to a function that returns a pointer to a function that returns a pointer to a function

(declare b (function-pointer (function-pointer (function-pointer int float) double) (long long int)))

in c it looks like this, shorter but not necessarily easier to read (notice how it is wrapped outside in)

int(*(*(*b)(long long int))(double))(float)


the characters "-", "->", "?", "!", which are often used in scheme, are allowed and replaced in identifiers. the replacements are done like guile does it. "-" becomes "_", "->" becomes _to_, "?" becomes "_p" (predicate) and "!" becomes "_x", some only in the middle or at the end of identifiers

if expressions

(set a (if* b 1 2)) -> a = (b ? 1 : 2)

goto labels

(label exit
  (return status))

string insertion

(sc-insert "// free c code string to be included as is")

scheme macros

sc supports non-hygienic macros with pattern matching.

(sc-define-syntax (for-each-index index limit body ...)
  (for ((define index size-t 0) (< index limit) (set+ index 1)) body ...))

(for-each-index i 10 (printf "%lu\n" i))

complex ellipsis patterns are possible

(sc-define-syntax (test x ((a b) ...) body ...)
  (x ((a ...) (b) ...) body ...))

sc-define-syntax* uses scheme expressions to generate the expansion and can return strings for plain c or scheme data for sc.

(sc-define-syntax* (test* a b ...)
  (let ((c 1))
    (cons* a c b)))

(sc-define-syntax* (test* a b ...)
  (quasiquote (if (unquote a) 1 (begin (unquote-splicing b)))))




install all dependencies



unpack the downloaded archive. for example

tar -xf sph-sc.tgz
  • -x is for extract
  • -f is for the source file name

without the installer

  • copy or symlink everything that is under "modules" into a directory listed when executing "guile -c '(display %load-path)'". a final example path is /usr/share/guile/site/sph/lang/sc.scm. also ensure that the user who will use the modules has the permissions to read the files
  • copy or symlink exe/sc into a directory listed when executing "echo $PATH". ensure that the execute permission is set ("chmod +x", "ls -l" rwx)

with the installer

the installer so far is only tested on gnu/linux and installs executables to /usr/bin by default.

cd sph-sc
su root

the install script has a "--help" and a "--dry-run" option for more options and information. the installer creates directories, copies files and sets permissions.


after everything is installed, run the following inside the sph-sc repository directory to see that everything works without errors.



with aurget: aurget -S --deps sph-sc-git

command-line application

$ sc --help

  options ... source-path ... destination-path
  options ... source-path
  --help | -h
  --parents  treat target as directory and recreate the directory structure of source files

usage from scheme

(import (sph lang sc))


(define result-c-string (sc->c (quote (begin (declare a int) (set a 1)))))

(define scheme-value 8)

(define code
      (declare a int)
      (set a (unquote scheme-value)))))

(sc->c code)

custom syntax can be defined from scheme before using sc->c with:

; symbol list list -> unspecified
(sc-define-syntax-scm id pattern expansion)

; symbol list {any ... -> string:c/any:sc} -> unspecified
(sc-define-syntax-scm* id pattern procedure)

(sc-define-syntax-scm* (quote test) (quote (a b ...))
  (lambda (a b) (cons* 0 a b)))

alternatively, the syntax table can be modified directly:

(hashtable-set! sc-syntax-table (quote myprefix)
  (lambda (a compile state)
    "list:without-prefix procedure:recurse:{a -> string} vector -> string:c/list:sc"
    (list (quote if) (car a) #t #f)))

bindings available in sc-define-syntax*

the current environment is:

(environment (q (guile)) (q (ice-9 match)) (q (sph lang sc eval-environment)))

custom environments can be passed to sc->c via sc-state, which can be created with sc-state-new. hints:

(sc-state-new load-paths eval-env)
(sc->c a load-paths state)


this repository includes under other/

  • an auto formatter "sc-format"
  • a documentation extractor "sc-documentor". it displays a list of declared types, enums, routines, macros and variables in markdown
  • an emacs mode "sph-sc-mode.el". example config in other/emacs
  • "scc" which compiles sc files directly with gcc


  • filename extension for source files: .sc

  • clang-format is a recommended auto formatter for c that also handles macro code relatively well (but doesnt cleanly format macros without semicolons). unfortunately, it cannot add empty lines between function definitions

  • sc only outputs valid c syntax

  • finding the source of c errors is usually the same as with plain c, particularly when the c code is formatted before compilation. modern c compilers indicate run-time errors with context and the directly translated c code is available

  • "sc-include" relative-paths are source-file relative unless they start with a slash. prefer standard pre-include instead of sc-include to not generate big, unwieldy c files

  • sc-macros are only included with sc-include

  • in sc-define-syntax*, (sc-gensym) and (sc-syntax? identifier) are available. the former returns a new identifier with each call, _t1, _t2 and so on, for temporary variable names

  • editor modes for scheme can be used and fast scheme-style structural editing is possible

  • indent-syntax (like coffeescript or python) can be used in combination with wisp. also see other/wisp2sc

  • square bracket array accessors can be used as long as they parse to scheme identifiers, for example (+ a[0] a[1])

  • the declare and set syntax lets things be grouped nicely

  • example code from projects using sc


macro usage and semicolons

in strict c11 mode, semicolons after blocks with braces are forbidden. when macros are used, sc does not know if the macro will expand to something that ends with a block, for example a function definition. compilers usually arent strict by default and dont even warn about this, but if you want to follow the standard strictly and have such a case, sc-no-semicolon can be used to prevent insertion of a semicolon. for example: (begin (sc-no-semicolon (mymacro 1)) (set b 2)) will lead to mymacro(1) instead of mymacro(1);

slightly different switch-case form

"case" in sc compiles to if/else-if and lets the user specify the equality predicate. see the section "syntax reference" below for an example. the syntax is:

(case predicate value-to-compare case-clause ...)
case-clause: ((value ...) consequent ...) / (value consequent ...)

this way it is possible to match values with =, but alternatively other predicates like custom comparison functions.

possible enhancements and ideas

  • better support for array types
    • (array x ...), as in declare. for type conversions, function parameters, typedef and dynamic array sizes
    • int(*)[3], int(*a)[3], typedef int(*a)[3], int[][3]
  • keyword arguments: it would be easy for sc to match guile style #:keywords with the parameter names of function definitions
  • module system: exports-form that compiles to nothing; import form that reads export-form from files and rewrites unexported identifiers to have less likely conflicting internal names. option to add prefix to imported bindings. bindings from preprocessor macros should be handled. or syntax for clang-modules
  • translate scheme comments. function and macro docstrings are translated as expected but scheme comments dont appear in c, only with (sc-comment "comment string") (or sc-insert). a scheme reader that parses scheme comments exists in sph-lib but it depends on another c library
  • better support for wisp, for example with a command-line flag. sc in wisp can be simplified if some replacements are made, for example alternated key/value listings (key value key/value ...) to ((key value) (key value) ...)
  • improve error messages. the existing checks and example patterns can be extended, and a better exception printer installed
  • try to reduce round brackets in the output, as there are cases where they are added when it is optional. this is difficult with arguments to preprocessor macros
  • support actual switch/case instead of only compiling to if/else
  • hygienic macros

syntax reference

sc expression and the c result. taken from the automated tests

(struct (pre-concat a b) (c (struct (pre-concat a b*))))
struct a##b{struct a##b* c;}

(declare a (array (struct b) 3))
struct b a[3];

(declare a (array (long unsigned int) 3))
long unsigned int a[3];

(begin #\newline)

(begin a--b)

(begin *a.b)

(struct-get (pointer-get a) b)

(struct-get (a b) c)

(if* #t (set a 1 b 2) 0)

(*a b)

(: ab cd)

(= a 1)

(and (= 1 2 3) (= 1 2))

(address-of a-b)

(and 1 2 3)

(and a (set b (c d)))

(array-get a 1)

(array-get (array-get a 1) 2)

(array-get aaa 3)

(array-get aaa 3 4 5)

(array-get *a 3)

(array-literal 1 "2" 3 4)

(array-set aa 0 11 1 22 3 33)

(array-set* a 2 3 4)

(begin "\"\"")

(begin "")

(begin "a")

(begin a?)

(begin a-b)

(begin a->b)

(begin a!)

(begin a!?<->)

(begin <)

(begin &a *a a.b)

(begin #t)

(begin -1)

(begin *a.field)

(begin &*a.field)

(begin &*a:b:c)

(begin 1 (begin 2 3))

(begin (enum (a b c d e)) (declare a int))
enum{a,b,c,d,e};int a;

(begin ab:cd:ef)

(begin ab:cd)

(bit-shift-right 1 2)

(bit-shift-left 1 2)

(bit-not a-b)

(case = myvalue ((3 2) #t) (4 #f) (("a" "b") #t #t) (else #f #f))
if((3==myvalue)||(2==myvalue)){1;}else if(4==myvalue){0;}else if(("a"==myvalue)||("b"==myvalue)){1;1;}else{0;0;}

(cond ((= a 1) #t))

(cond ((= a 1) (= b 2)) ((= c 3) #t))
if(a==1){(b==2);}else if(c==3){1;}

(cond ((= a 1) (= b 2)) ((= c 3) #t) (else 4))
if(a==1){(b==2);}else if(c==3){1;}else{4;}

(cond* ((= a 1) (= b 2)) ((= c 3) #f #t) (else #t #f))

(convert-type abc int)

((convert-type abc function-t) d e)

(convert-type a (function-pointer void void*))

(declare aa (array size-t (b-b)))
size_t aa[b_b];

(declare aa (array size-t (1 2 3) (array-literal (array-literal -4 5 test-c) (array-literal 6 7 8))))
size_t aa[1][2][3]={{{-4,5,test_c},{6,7,8}}};

(declare a (struct test))
struct test a;

(declare a (struct (test int)))
struct a{int test;};

(declare type-name (type (function-pointer type-return type-argument-1 type-argument-2)))
typedef type_return(*type_name)(type_argument_1,type_argument_2);

(declare test-t (type (enum (a b c))))
typedef enum{a,b,c} test_t;

(declare e (type (struct (a-b (function-pointer b c-e d)) (b i-nt))))
typedef struct{b(*a_b)(c_e,d);i_nt b;} e;

(declare a uint32_t (b ba bb) (void uint8_t uint32_t))
uint32_t a;void b(uint8_t ba,uint32_t bb);

(declare c (array uint8_t (3 4)) c (array uint8_t 5) c (array uint8_t (2) 0 0))
uint8_t c[3][4];uint8_t c[5];uint8_t c[2]={0,0};

(declare e (enum (ea eb ec)) d (struct (da (unsigned int))))
enum{ea,eb,ec};struct d{unsigned int da;};

(declare f (type uint8_t) g (type (struct (ga (unsigned int)))))
typedef uint8_t f;typedef struct{unsigned int ga;} g;

(declare h (struct-variable ha 0 0))
ha h={0,0};

(declare (pre-concat h i) uint32_t)
uint32_t h##i;

(define a uint32_t 1)
uint32_t a=1

(define a uint32_t 1 b uint64_t 2)
uint32_t a=1;uint64_t b=2

(declare a (function-pointer (function-pointer (unsigned int) float) double))
unsigned int(*(*a)(double))(float);

(declare a (function-pointer (function-pointer (function-pointer int float) double) (long long int)))
int(*(*(*a)(long long int))(double))(float);

(define (a) (function-pointer uint32_t uint64_t) #t)

(define (a b) ((function-pointer uint32-t uint64_t) (function-pointer uint32-t uint64_t)) #t)

(define (a b) ((function-pointer (function-pointer uint32_t b-16) uint8_t) b-64))
uint32_t(*(*a(b_64 b))(uint8_t))(b_16)

(define (pre-concat a b) uint32_t 1)
uint32_t a##b=1

(define (abc) uint32_t (return 0))
uint32_t abc(){return(0);}

(define (abc d e) (uint32_t uint64_t b16) (return 0))
uint32_t abc(uint64_t d,b16 e){return(0);}

(define (abc d e) (uint32_t (pre-concat b 64) b16) (return 0))
uint32_t abc(b##64 d,b16 e){return(0);}

(define (a) void "test-docstring")
/** test-docstring */
void a()

(define (a b) (c d) "e")
/** e */
c a(d b)

(define (a b c) (void void void) "test-docstring" (+ b c))
/** test-docstring */
void a(void b,void c){(b+c);}

(do-while #t 1 2 3)

(enum (a b c d e))

(enum (a b (c 3) d (e 4)))

(enum test (a b c d e))
enum test{a,b,c,d,e}

(for ((set index 0) (< index len) (set index (+ 1 index))) #t)

(for (((set a 0) (set b 1)) (< index len) ((set a (+ 1 a)) (set b (+ 2 b)))) #t)

(for ((begin a b) (< c d) (begin e f)) #t)

(function-pointer void vo-id*)

(if (= a 3) (exit 1) (return (bit-or b c)))

(if 1 2 (begin 3 4 (return #t)))

(if* a (if* b c d) e)

(if* a (if* (if* b c) d) e)

(if* (= a 3) (begin (set b-c 4) (myproc a b-c)) a)

(if* (not a) #t #f)

(if* (not 1) a b)

(label abc (define a uint32_t 3) (+ a b))
abc:uint32_t a=3;(a+b);

(let* ((a size_t 1) (b size_t 2) (c 3)) (set c 7) (return (if* 4 5 6)))
{size_t a=1;size_t b=2;c=3;c=7;return((4?5:6));}

(not 1)

(pointer-get a-b)

(pointer-get (a b))

(pointer-get b)

(pointer-get b.c)

(pre-concat a b cd e)

(pre-cond ((= a b) 1))
#if (a==b)

(pre-cond ((= a b) 1) (c (pre-define a)) (else 2))
#if (a==b)
#elif c
#define a

(pre-cond-defined (a 1) (b 2))
#ifdef a
#elif b

(pre-cond-not-defined (a 1))
#ifndef a

(pre-define a)
#define a

(pre-define (my-macro a b) (if* a #t #f))
#define my_macro(a,b) (a?1:0)

(pre-define (a) #t)
#define a() 1

(pre-define (a b) (begin "test-docstring" (+ b c) 3))
/** test-docstring */
#define a(b) (b+c);3

(pre-define ob-ject 3)
#define ob_ject 3

(pre-define a 1 (id a b) (= a b))
#define a 1
#define id(a,b) (a==b)

(pre-define a 1 (id) b)
#define a 1
#define id() b

(pre-define (->test a b) c)
#define _to_test(a,b) c

(pre-if (= a b) (begin c d e) (begin f g))
#if (a==b)

(pre-if-not-defined a b c)
#ifndef a

(pre-if-defined a b c)
#ifdef a

(pre-include "./a/b.c")
#include "./a/b.c"

(pre-include "../a/b.c")
#include "../a/b.c"

(pre-include "a/b.c")
#include <a/b.c>

(pre-include "bb.h")
#include <bb.h>

(pre-include "a" "b" "./c")
#include <a>
#include <b>
#include "./c"

(pre-let* (a 1 b 2) (+ a b))
#define a 1
#define b 2
#undef a
#undef b

(pre-let* (a 1) a)
#define a 1
#undef a

(pre-let* ((a b) 1) a)
#define a(b) 1
#undef a

(pre-let* ((a b) 1 (c d) 2) a)
#define a(b) 1
#define c(d) 2
#undef a
#undef c

(pre-pragma once)
#pragma once

(pre-stringify abc)

(pre-undefine my-macro)
#undef my_macro


(return 1 2)

(sc-insert "var a = 3")
var a = 3

(set *a *b.c)

(set a 1)

(set a 1 b-2 2 c-3 3)

(set a:b (: *a b))

(struct-get a b)

(struct-get a b c d e)

(struct-get (pointer-get a) b)

(struct-get **a b)

(struct (a (unsigned int)) (b (unsigned char) 3))
struct{unsigned int a;unsigned char b:3;}

(struct testname (a (uns-igned int)) (b (unsigned char) 3))
struct testname{uns_igned int a;unsigned char b:3;}

(struct (a-b (function-pointer b c-e d)) (b i-nt))
struct{b(*a_b)(c_e,d);i_nt b;}

(struct-literal (a 1) (b "2"))

(struct-literal a 1)

(struct-set a b 1 c 2)

(struct-pointer-get a b)

(struct-pointer-get a b c d)

(union (a (unsigned int)) (b (unsigned char) 3))
union{unsigned int a;unsigned char b:3;}

(while #t 1 2 3)

(while (not (= 0 (set a (b c)))) #t)

(sc-comment "abc")
/* abc */

(sc-comment "abc" "def" "ghi")
/* abc
ghi */

(!= 1 2 3)

(begin (sc-no-semicolon (a 1)) (set b 2))

(begin (sc-no-semicolon (a 1) (set b 2)))

(begin (pre-define a (begin (define (a) void 1))) (declare b int))
#define a void a(){1;}
int b;

(begin (pre-define (a b) (define (c) void 1)) (a "xyz"))
#define a(b) void c(){1;}

(set+ a 1 b 2)

(set- a 1)

(set* a 1)

(set/ a 1)

(declare a (type (struct (b (array int 3)))))
typedef struct{int b[3];} a;

(pre-define-if-not-defined abc 3 def 4)
#ifndef abc
#define abc 3
#ifndef def
#define def 4

(pre-define (a) (begin 1 (sc-comment "b") 2 3))
#define a() 1;\
/* b */\

(case* = myvalue ((3 2) #t) (4 #f) (("a" "b") #t #t) (else #f #f))

(for ((set a 1 b 2) #t (set c 3 d 4)) #t)

(begin (pre-define (a) (begin "test" b) c d) (declare e f))
/** test */
#define a() b
#define c d
f e;

similar projects

  • lispc - lisp(ish) to c converter (designed for clisp)
  • sxc - 's-expression c' transpiler for generating c code using macros written in common lisp


compiles scheme-like s-expressions to c