Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ TODO:
Reading error messages
.jank, .cljc, .cpp files
All modules need to intern the ns
nREPL support
AOT compiling programs (and distributing them)
Printing codegen
Known issues
Expand Down Expand Up @@ -45,3 +44,4 @@ TODO:
* [Checking jank's health](troubleshooting/health-check.md)
* [How to get a stack trace](troubleshooting/stack-trace.md)
* [Where to get help](troubleshooting/getting-help.md)
* [FAQ](troubleshooting/faq.md)
173 changes: 165 additions & 8 deletions book/src/cpp-interop/native-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,177 @@ C++ types are available within the `cpp` namespace, if you replace `::` with
type aliases.

## Complex literal types
If your C++ type is not representable using jank's interop syntax, due to
template arguments or other shenanigans, you can use `cpp/type` to provide the
complete type using an inline C++ string. For example, here's how we both grab
the type and construct it (call the type), to get a value. In this example, we
build a C++ ordered map from `std::string` to pointers to functions which take
in an `int` and return an `int`.
jank supports a C++ type
domain-specific language (DSL) which can express any C++ type. This is available
implicitly when in type position, but it can be explicitly requested by using
the special `cpp/type` form. The `cpp/type` form resolves a type using the
following DSL:

* `(:* t)` adds a pointer
* `(:& t)` adds an lvalue reference
* `(:&& t)` adds an rvalue reference
* `(:const t)` and `(:volatile t)` add the corresponding C++ qualification
* `(:signed t)` and `(:unsigned t)` add the corresponding C++ qualification
* `(:long t)` and `(:short t)` add the corresponding C++ qualification
* `(:array t s?)` turns a type into a sized (or unsized) array
* `(:fn ret [a1 a2...])` builds a function type
* `(t a1 a2...)` build a template instantiation
* `(:member t name)` nests into a type
* `(:member* t mt)` builds a pointer to member

Let's take a look at some examples, comparing the C++ representation and the
jank representation.

<table>
<tr>
<td> C++ </td> <td> jank </td>
</tr>

<tr>
<td>

A normal C++ map template instantiation.

```cpp
std::map<std::string, int*>
```

</td>
<td>

```clojure
(std.map std.string (:* int))
```

</td>
</tr>

<tr>
<td>

A normal C++ array template instantiation.

```cpp
std::array<char, 64>::value_type
```

</td>
<td>

```clojure
(:member (std.array char 64) value_type)
```

</td>
</tr>

<tr>
<td>

A sized C-style array.

```cpp
unsigned char[1024]
```

</td>
<td>

```clojure
(:array (:unsigned char) 1024)
```

</td>
</tr>

<tr>
<td>

A reference to an unsized C-style array.

```cpp
unsigned char(&)[]
```

</td>
<td>

```clojure
(:& (:array (:unsigned char)))
```

</td>
</tr>

<tr>
<td>

A pointer to a C++ function.

```cpp
int (*)(std::string const &)
```

</td>
<td>

```clojure
(:* (:fn int [(:& (:const std.string))]))
```

</td>
</tr>

<tr>
<td>

A pointer to a C++ member function.

```cpp
int (Foo::*)(std::string const &)
```

</td>
<td>

```clojure
(let [m ((cpp/type "std::map<std::string, int (*)(int)>"))]
)
(:member* Foo (:fn int [(:& (:const std.string))]))
```

</td>
</tr>

<tr>
<td>

A pointer to a C++ member which is itself a pointer to a function.

```cpp
void (*Foo::*)()
```

</td>
<td>

```clojure
(:member* Foo (:* (:fn void [])))
```

</td>
</tr>

</table>


## Defining new types
There isn't yet a way to define new types using jank's syntax, but you can
always drop to `cpp/raw` to either include headers or define some C++ types
inline. Improved support for extending jank's object model with JIT (just in
time) compiled types will be coming in 2026.

```clojure
(cpp/raw "struct person
{
std::string name;
};")
```
3 changes: 3 additions & 0 deletions book/src/cpp-interop/native-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,6 @@ complete value using an inline C++ string. For example, here's how we grab the
No implicit boxing will happen here, unless you use this value in a way which
requires it. jank will give you a reference to the value you specified. If you
need a copy, you will need to manually do that.

* `(t a1 a2...)` build a template instantiation
* `(:member t name)` nests into a type
74 changes: 74 additions & 0 deletions book/src/troubleshooting/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Frequently asked questions (FAQ)

## Is jank compatible with Clojure?
Yes! jank is a Clojure dialect. Furthermore, I am working directly with the Clojure
core team and jank is sponsored both by Nubank and Clojurists Together. That's
leads us to the next question, though.

## What is a Clojure dialect?
Now this is the question. Let's survey the landscape of Clojure dialects.

* [ClojureScript](https://www.clojurescript.org/about/differences)
* Doesn't have reified vars and `def` just create JS globals
* Doesn't have refs or software transactional memory (STM)
* Doesn't have a character type
* Doesn't have a ratio, big decimal, or big integer type
* Runs macros in a different compilation stage (in Clojure JVM)
* Supports a custom `#js` reader tag
* And so on
* [Clojure Dart](https://github.com/Tensegritics/ClojureDart/blob/main/doc/differences.md)
* Lazily initializes `def`, to aid in tree shaking
* Doesn't have multi-methods
* Extends `catch` syntax to support stack traces
* Runs macros in a different compilation stage (in Clojure JVM)
* Doesn't have array maps
* Supports named parameters
* Supports a custom `#dart` reader tag
* And so on
* [Basilisp](https://docs.basilisp.org/en/latest/differencesfromclojure.html)
* All numbers are unlimited precision
* Doesn't have refs or software transactional memory (STM)
* Doesn't have a character type
* Supports a custom `#py` reader tag
* Python builtins are available under the special `python/` namespace
* And so on
* jank
* You can read the differences [here](../differences-from-clojure.md)

The same sorts of differences can be found for most Clojure dialects. A crucial reason
that each Clojure dialect is different is that Clojure is designed to embrace
its host runtime. By that I mean that Clojure JVM leans into the JVM.
ClojureScript leans into JavaScript. Clojure Dart leans into the Dart world. In
each of these, attributes and behaviors of the host runtime show transparently
through the Clojure dialect. They're not hidden. This, on the surface, makes
Clojure dialects different, but actually it's for this reason that they're all
more Clojure-like.

So what's common across all of these? That's not currently defined by the
Clojure core team. But the common space across all of these is where jank aims
to meet. A good mantra for this is "If it works in Clojure JVM and
ClojureScript, it should work in jank."

For more info on the differences between dialects and how they're tracked, take
a look at the [clojure test suite](https://github.com/jank-lang/clojure-test-suite),
which is a jank-lead initiative to find unexpected discrepancies across
dialects.

## Why does jank have its own file type?
That's what all Clojure dialects do. If you want code which can run on multiple
dialects, use a `.cljc` (Clojure Common) file with reader conditionals.

* Clojure JVM: `.clj`
* ClojureScript: `.cljs`
* Clojure CLR: `.cljr`
* Clojure Dart: `.cljd`
* Babashka: `.bb`
* Basilisp: `.lpy`
* jank: `.jank`

## How is jank's memory managed?
The Clojure side of jank is garbage collected, using the Boehm GC (BDWGC).
However, any C++ interop uses normal C++ idioms, including RAII. For example, if
you stack allocate a C++ value which has a destructor, jank will ensure that
destructor runs at the end of the object's scope. Also, you can use `cpp/new`
and `cpp/delete` or `cpp/malloc` and `cpp/free`, if you so desire.
3 changes: 3 additions & 0 deletions book/src/troubleshooting/getting-help.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Where to get help
> [!NOTE]
> Before reaching out with questions, check the [FAQ](./faq.md).

The jank community, which is the Clojure community, is known to be welcoming.
You are encouraged to reach out if you have any issues. Firstly, drop into
[Slack](https://clojurians.slack.com/archives/C03SRH97FDK)
Expand Down
1 change: 1 addition & 0 deletions book/src/troubleshooting/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ leaks, or other issues. In this chapter, we'll learn the following:
1. How to check the health of your jank install
2. How to get a stack trace for a jank crash
3. Where to ask for help and report issues
4. Other frequently asked questions
1 change: 1 addition & 0 deletions compiler+runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ add_library(
src/cpp/jank/runtime/core/math.cpp
src/cpp/jank/runtime/core/meta.cpp
src/cpp/jank/runtime/core/call.cpp
src/cpp/jank/runtime/sequence_range.cpp
src/cpp/jank/runtime/perf.cpp
src/cpp/jank/runtime/module/loader.cpp
src/cpp/jank/runtime/object.cpp
Expand Down
13 changes: 10 additions & 3 deletions compiler+runtime/include/cpp/jank/analyze/cpp_util.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <clang/Interpreter/CppInterOp.h>
#include <CppInterOp/CppInterOp.h>

#include <jtl/ptr.hpp>
#include <jtl/result.hpp>
Expand All @@ -18,9 +18,10 @@ namespace jank::analyze::cpp_util
};

jtl::string_result<void> instantiate_if_needed(jtl::ptr<void> const scope);
jtl::string_result<jtl::ptr<void>>
instantiate(jtl::ptr<void> const scope, native_vector<Cpp::TemplateArgInfo> const &args);

jtl::ptr<void> apply_pointers(jtl::ptr<void> type, u8 ptr_count);
jtl::ptr<void> resolve_type(jtl::immutable_string const &sym, u8 ptr_count);
jtl::ptr<void> resolve_type(jtl::immutable_string const &sym);
jtl::string_result<jtl::ptr<void>> resolve_scope(jtl::immutable_string const &sym);
jtl::string_result<jtl::ptr<void>> resolve_literal_type(jtl::immutable_string const &literal);
jtl::string_result<literal_value_result>
Expand Down Expand Up @@ -49,14 +50,18 @@ namespace jank::analyze::cpp_util
bool is_typed_object(jtl::ptr<void> type);
bool is_any_object(jtl::ptr<void> type);
bool is_primitive(jtl::ptr<void> type);
bool is_constructible(jtl::ptr<void> to_type, jtl::ptr<void> from_type);
bool is_member_function(jtl::ptr<void> scope);
bool is_non_static_member_function(jtl::ptr<void> scope);
bool is_nullptr(jtl::ptr<void> type);
bool is_implicitly_convertible(jtl::ptr<void> from, jtl::ptr<void> to);
bool is_pointer_to_void_conversion(jtl::ptr<void> from, jtl::ptr<void> to);

jtl::ptr<void> base_type(jtl::ptr<void> type);

jtl::ptr<void> untyped_object_ptr_type();
jtl::ptr<void> untyped_object_ref_type();
jtl::ptr<void> char_type();

usize offset_to_typed_object_base(jtl::ptr<void> type);

Expand All @@ -65,6 +70,8 @@ namespace jank::analyze::cpp_util

jtl::result<void, error_ref> ensure_convertible(expression_ref const expr);

native_vector<jtl::ptr<void>> aggregate_initialization_types(jtl::ptr<void> scope);

enum class implicit_conversion_action : u8
{
unknown,
Expand Down
4 changes: 4 additions & 0 deletions compiler+runtime/include/cpp/jank/analyze/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ namespace jank::analyze
expression_position,
jtl::option<expr::function_context_ref> const &,
bool needs_box);
jtl::result<jtl::ptr<void>, error_ref>
analyze_type(runtime::object_ref const,
local_frame_ptr,
jtl::option<expr::function_context_ref> const &);
expression_result analyze_call(runtime::obj::persistent_list_ref const,
local_frame_ptr,
expression_position,
Expand Down
3 changes: 3 additions & 0 deletions compiler+runtime/include/cpp/jank/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ namespace jank::error
analyze_invalid_cpp_raw,
analyze_invalid_cpp_type,
analyze_invalid_cpp_type_position,
analyze_invalid_cpp_type_dsl,
analyze_invalid_cpp_value,
analyze_invalid_cpp_cast,
analyze_invalid_cpp_unsafe_cast,
Expand Down Expand Up @@ -295,6 +296,8 @@ namespace jank::error
return "analyze/invalid-cpp-type";
case kind::analyze_invalid_cpp_type_position:
return "analyze/invalid-cpp-type-position";
case kind::analyze_invalid_cpp_type_dsl:
return "analyze/invalid-cpp-type-dsl";
case kind::analyze_invalid_cpp_value:
return "analyze/invalid-cpp-value";
case kind::analyze_invalid_cpp_cast:
Expand Down
3 changes: 3 additions & 0 deletions compiler+runtime/include/cpp/jank/error/analyze.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ namespace jank::error
runtime::object_ref const expansion);
error_ref analyze_invalid_cpp_type_position(read::source const &source,
runtime::object_ref const expansion);
error_ref analyze_invalid_cpp_type_dsl(jtl::immutable_string const &message,
read::source const &source,
runtime::object_ref const expansion);
error_ref analyze_invalid_cpp_value(jtl::immutable_string const &message,
read::source const &source,
runtime::object_ref const expansion);
Expand Down
Loading
Loading