Updated in 2022-10.
In the afternoon, I came cross the Nim programming language again on Lobsters. I first learned some basics of the language in 2015, but had not touched it since then.
"Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula.", according to its website.
Basic features: parametric polymorphism. Advanced features: macros (including term-rewriting macros), compile-time function execution, effect system, concepts
An idea popped into my mind: why not solve some coding challenges in Nim?
As a niche language, it is not supported on many coding challenge websites. Fortunately, the Nim compiler generates C code. With a small amount of work, we can build a self-contained C source file suitable for submission.
Let's take a LeetCode challenge as an example. We write the main algorithm in Nim and use the emit pragma to write a C wrapper.
1 | # Maximum Number of Events That Can Be Attended II |
Then, create a self-contained C file with the approach described on https://zeta.su/posts/amalgamating-nim-programs/.
Install opam
Follow https://opam.ocaml.org/doc/Install.html and run
opam init
.
Build CIL
Clone https://github.com/goblint/cil. Use a modestly recent
version of ocaml or pick a recent ocaml release from
opam switch list-available
.
1 | % opam switch create . |
Patch nimbase.h
Copy lib/nimbase.h
to ~/Util/Nim
. Comment
out a visibility attribute. 1
2
3
4
5
6
7
8
9
# define N_LIB_EXPORT_VAR __declspec(dllexport)
# define N_LIB_IMPORT extern __declspec(dllimport)
#else
-# define N_LIB_PRIVATE __attribute__((visibility("hidden")))
+# define N_LIB_PRIVATE //__attribute__((visibility("hidden")))
# if defined(__GNUC__)
# define N_CDECL(rettype, name) rettype name
# define N_STDCALL(rettype, name) rettype name
In 2021 goblint-CLI did not handle #define _GNU_SOURCE 1
but it can now.
Generate C amalgamation
On Linux the Nim compiler calls gcc by default. We replace gcc with a CIL wrapper to generate a merged C file.
1 | % cat Makefile |
Then run nim c -d:danger --gc:arc -d:useMalloc a.nim
to
generate a_comb.c
. -d:useMalloc
avoids Nim's
own memory manager and can greatly decrease the C code size. There are
several choices for --mm
, but --mm:arc
can
genreate the smallest C code.
a_comb.c
looks like:
1 | /* Generated by CIL v. 1.8.1 */ |
The LeetCode environment includes some glibc headers like
<stdio.h>
which result in some conflicts due to
duplicate definitions of struct _IO_FILE
and some GNU
extern inline functions. Let's write a Nim program to remove the
duplicate definitions.
1 | import strutils |
1 | nim c --run --skipProjCfg --verbosity:0 x > amalgamation.c |
Finally, run
{ echo '/*'; cat a.nim; echo '*/'; cat amalgamation.c; } | xclip -i -selection clipboard
and paste the content into the LeetCode editor:) Well, the Nim source is
not really needed but it is useful for archive purposes.
Minimization
Now let's decrease the size to make the amalgamation fit into more platforms.
clang-format safely removes whitespace and makes the program smaller.
1
2
3
4
5
6% cat .clang-format
ColumnLimit: 9999
IndentWidth: 0
ContinuationIndentWidth: 0
SpaceBeforeAssignmentOperators: false
SpaceBeforeParens: Never
To further decrease the source code length, we can shorten function, variables, and type names. See C minifier with Clang.
Old notes
If CIL fails to handle some syntax, use another deduplicator:
1 | #!/usr/bin/env python |
1 | cat ~/.cache/nim/a_r/*.c | ./dedup.py > for-submit.c |