The following types are now changed:
- * is now addr
- & is now ref
- [] is now array
- <> is now template
- && is deprecated
I did this because I believe symbol-heavy languages end up harder to
read and understand. Now, we can do things that make a lot of sense:
(var my-int-ptr (addr int) (addr an-int))
It also means less utilization of the shift key, which is nice
ergonomics-wise. The tradeoff of course is more typing, but I consider
pressing a single key quite a bit easier than shift and a key.
The following generators are now deprecated:
++
--
%
block
The first three are in the same spirit as the type name
change. Symbols are harder to search and type than words.
These are now renamed:
bit->> becomes bit-shift->>
bit-<< becomes bit-shift-<<
This is an obnoxious change, but I figure because of low utilization
by people other than myself, now is as good a time as any.
@ -73,21 +73,20 @@ By default, ~&with-defs~ is specified.
You shouldn't expect Cakelisp features to work with external C/C++ code. Features like hot-reloading or introspection aren't available to external code because Cakelisp does not parse any C/C++ headers. This doesn't mean you cannot call C/C++ code from a hot-reloaded Cakelisp function, it just means you cannot magically hot-reload the C/C++ code you're calling.
* Types
Types are identical to types in C, but specified in an S-expression notation. Here are some example C++ types and their corresponding Cakelisp:
| ~std::vector<int>~ | ~(template (in std vector) int)~ |
| ~std::map<std::string, int>~ | ~(template (in std map) (in std string) int)~ |
| ~int&~ | ~(ref int)~ |
| ~int&&~ | ~(rval-ref-to int)~ |
Note that C++ scope resolution operator can be used or ~in~ can be used. The latter is preferable.
@ -95,7 +94,7 @@ While this is more verbose than C types, they are much more easily parsed and co
To read C types properly, you must [[http://unixwiz.net/techtips/reading-cdecl.html][work backwards from the name]] and apply several heuristics. The parentheses do add more typing, but they're more clear, machine-parseable, and can be read naturally (e.g. read left to right "pointer to constant character" vs. C's "constant character pointer", which seems worse in my mind).
This form also handles arrays as part of the type: ~(var my-array ([] 5 int))~ rather than ~int myArray[5];~, another way it is more consistent, readable, and parsable.
This form also handles arrays as part of the type: ~(var my-array (array 5 int))~ rather than ~int myArray[5];~, another way it is more consistent, readable, and parsable.
You can use any C/C++ keywords like ~volatile~, ~unsigned~, ~struct~, etc. in the same way that ~const~ is demonstrated above.
* Functions
@ -153,20 +152,20 @@ Use ~set~ to modify variables:
Arrays have the same syntactic sugar as C, e.g.:
#+BEGIN_SRC C
(var my-numbers ([] int) (array 1 2 3))
(var my-numbers (array int) (array 1 2 3))
#+END_SRC
...is a better way than
#+BEGIN_SRC C
(var my-numbers ([] 3 int) (array 1 2 3))
(var my-numbers (array 3 int) (array 1 2 3))
#+END_SRC
...because the compiler will automatically determine the size.
* Type aliases
Aliases can be created for types. Internally, this uses ~typedef~. For example:
@ -193,7 +192,7 @@ The syntax for function pointers is shown in [[file:../runtime/HotLoader.cake][H
If you wanted to define a function pointer which could point to ~int main(int numArguments, char* arguments[])~, for example:
#+BEGIN_SRC lisp
(def-function-signature main-signature (num-arguments int
arguments ([] (* char))
arguments (array (addr char))
&return int))
(var main-pointer main-signature (addr main))
#+END_SRC
@ -213,22 +212,22 @@ Use the argument ~--list-built-ins~ to see an up-to-date list of all possible ex
- ~cond~
- ~when~:
- ~unless~:
- ~array~: Used for initializer lists, e.g. ~(var my-array ([] int) (array 1 2 3))~. Without arguments, equals the default initializer, e.g. ~(array)~ becomes ~{}~ in generated code
- ~array~: Used for initializer lists, e.g. ~(var my-array (array int) (array 1 2 3))~. Without arguments, equals the default initializer, e.g. ~(array)~ becomes ~{}~ in generated code
- ~set~: Sets a variable to a value, e.g. ~(set my-var 5)~ sets ~(var my-var int)~ to ~5~
- ~block~: Defines a scope, where variables declared within it are limited to that scope
- ~scope~: Alias of block, in case you want to be explicit. For example, creating a scope to reduce scope of variables vs. creating a block to have more than one statement in an ~(if)~ body
- ~?~: Ternary operator. For example, the expression ~(? true 1 2)~ will return 1, whereas ~(? false 1 2)~ returns 2. Handy for when you don't want to use a full ~if~ statement, for example
** Pointers, members
- ~new~: Calls C++ ~new~ with the given type, e.g. ~(new (* char))~ will allocate memory for a pointer to a character
- ~new~: Calls C++ ~new~ with the given type, e.g. ~(new (addr char))~ will allocate memory for a pointer to a character
- ~deref~: Return the value at the pointer's address
- ~addr~: Take the address of a variable/member
- ~field~: Access a struct/class member. For example, with struct ~(defstruct my-struct num int)~, and variable ~(var my-thing my-struct)~, access ~num~: ~(field my-thing num)~
- ~path~: Access fields from struct addresses. For example, ~(path my-struct-ptr > num)~. You can nest these and access non-pointer fields as well, e.g. ~(path my-struct . ptr-field > another-struct . field)~. Path removes the need to do e.g. ~(field (deref my-struct-ptr) num)~, which can become hard to read with deep accessing.
- ~call-on~: Call a member function. For example, if I have a variable ~my-bar~ of type ~Bar~ with member function ~do-thing~, I can call it like so: ~(call-on do-thing my-bar arg-1 arg-2)~
- ~call-on-ptr~: Like ~call-on~, only it works on pointers, e.g. ~(var my-pointer-to-bar (* Bar) (addr a-bar))~, call its member: ~(call-on-ptr do-thing my-pointer-to-bar arg-1 arg-2)~. These can be nested as necessary
- ~call-on-ptr~: Like ~call-on~, only it works on pointers, e.g. ~(var my-pointer-to-bar (addr Bar) (addr a-bar))~, call its member: ~(call-on-ptr do-thing my-pointer-to-bar arg-1 arg-2)~. These can be nested as necessary
- ~call~: Call the first argument as a function. This is necessary when you can't type the function's name directly, e.g. it is a function pointer. For example, to call a static member function: ~(call (in my-class do-static-thing) arg-1 arg-2)~
- ~in~: Scope resolution operator (~::~). Used for both namespaces and static member access. For e.g. ~(in SuperSpace SubSpace Thing)~ would generate ~SuperSpace::SubSpace::Thing~. ~in~ may be used within type signatures
- ~type-cast~: Cast the variable to given type, e.g. ~(var actually-int (* void) (get-stored-var-pointer "my-int"))~ could become an int via ~(type-cast actually-int (* int))~
- ~type-cast~: Cast the variable to given type, e.g. ~(var actually-int (addr void) (get-stored-var-pointer "my-int"))~ could become an int via ~(type-cast actually-int (addr int))~
- ~type~: Parse the first argument as a type. Types are a domain-specific language, so the evaluator needs to know when it should use that special evaluation mode
** Logical expressions
- ~not~: Inverts the boolean result of the argument. ~(not true)~ equals ~false~
@ -323,7 +322,7 @@ The binding would result like so:
We could output a variable declaration like so:
#+BEGIN_SRC lisp
(var (<> std::vector Token) initializer)
(var (template (in std vector) Token) initializer)
@ -345,7 +344,7 @@ Macros must return ~true~ or ~false~ to denote whether the expansion was success
*** ~tokenize-push~
~tokenize-push~ is the main "quoting" function. The first argument is the output variable. ~output~ is passed in to ~defmacro~ automatically, but you can define other token arrays like so:
#+BEGIN_SRC lisp
(var my-tokens (<> std::vector Token))
(var my-tokens (template (in std vector) Token))
#+END_SRC
~tokenize-push~ copies all source tokens directly to the output until it reaches one of the ~token*~ functions. These functions tell the tokenizer to unpack and insert the tokens in the variables rather than the symbol which is the variable name. Unless otherwise specified, these take any number of arguments:
@ -484,9 +483,9 @@ This configuration label ensures Cakelisp itself doesn't get affected by your ru
There may be cases when you need to do complex logic or modifications of the link command. We use a ~hook~ to give us a chance to do so.
@ -355,7 +355,7 @@ Each hook has a pre-defined signature, which is what the ~environment~ and other
From our previous note on ~post-references-resolved~ we learned that our hook can be invoked multiple times. Let's store a comptime var to prevent it from being called more than once:
@ -404,7 +404,7 @@ To do this, we will generate a new array of Tokens and tell Cakelisp to evaluate
We need to create the Token array such that it can always be referred back to in case there are errors. We do this by making sure to allocate it on the heap so that it does not go away on function return or scope exit:
#+BEGIN_SRC lisp
(var command-data (* (<> std::vector Token)) (new (<> std::vector Token)))
(var command-data (addr (template (in std vector) Token)) (new (template (in std vector) Token)))
Now the runtime knows what the layout of the data is. In ~create-command-lookup-table~, let's generate another array of tokens to hold the runtime lookup table variable:
#+BEGIN_SRC lisp
(var command-table-tokens (* (<> std::vector Token)) (new (<> std::vector Token)))
(var command-table-tokens (addr (template (in std vector) Token)) (new (template (in std vector) Token)))