This is the official BluePrint Compiler (bpc) It can be used to compile the BluePrint lisp flavour to javascript
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Jakob Meier 05478a502f
finished version 2
3 weeks ago
assets Added Icon 8 months ago
doc Added documentation 8 months ago
examples added classes 3 weeks ago
scripts added watch script 5 months ago
src added classes 3 weeks ago
templates added string replace template 5 months ago
.gitignore removed dotfiles 5 months ago finished version 2 3 weeks ago updated contributing guide 4 months ago
Cargo.lock added templated commands 5 months ago
Cargo.toml finished version 2 3 weeks ago
LICENSE Updated license content from 8 months ago added classes 3 weeks ago

NOTE: SchemeScript is now BluePrint

NOTE: The official BluePrint git-repository is hosted on codeberg and mirrored to GitLab and GitHub. Only contributions on codeberg will be accepted

About BluePrint

/comcloudway/blueprint/src/branch/main/assets/icon.png BluePrint lisp to javascript transpiler

Official BluePrint Icon


BluePrint is a lisp to javascript transpiler written in rust. As every other transpiler it is used to convert source code of one language into the source code of another. This allows you to write your software in the BluePrint Lisp Flavour and transpile it to valid javascript.


BluePrint (back then called SchemeScript) was split from cracker, my lisp interpreter (still WIP), after I watched the video about fennel. As many other people - I kind of get annoyed with javascript after working with it for some time. And being able to use lisp is a great distraction from all those problems, while still being able to create the same product.


  • nearly full Javascript compatibility: At this point BluePrint is able to convert most of the neccessary javascript functions, with more being added each release. It also features a fallback converter to convert most of the missing functions automatically
  • runtime code is not dependent on BluePrint: After transpiling lisp to javascript, you can execute that code everywhere where you would normally be able to use javascript, no need to keep the BluePrintTranspiler around
  • write your own: If you feel like BluePrint is missing some commands, you can simply use it as a rust library and add your own commands, keywords or macros.


Check out the file

Getting Started

NOTE: We currently do not provide prebuild binaries


  • rustc, cargo (to be able to compile the binary)
  • git (to clone the project)

Getting the source

Download the latest release as a zip file, or clone the git repository. On Linux this can be achieved by running

git clone

NOTE: Some Linux distributions don't ship with git preinstalled Now that you have downloaded the source code, navigate into the blueprint directory you just created.

cd blueprint

[OPTIONAL] Running the Project

This step is optional and not recommened if you want to install the binary. Running the project in advance will allow you to see if there are problems with your system. The project can be run by using cargo.

cargo run --

Building the binary

After successfully fetching the source code, it is time to build the binary - this is basically the executable you use to transpile from lisp to js. As this project is using the cargo package manager, you can use cargo to build the binary.

cargo build --release

NOTE: The --release flag enables optimisation, making BluePrint even faster You are technically good to go now. The binary you just build can be found in the target/ folder. If you are running Linux you can execute it by running


[RECOMMENDED] [OPTIONAL] Adding the binary to your $PATH

Right now executing the binary involves you navigating into the project directory and executing the binary from there. To have user-wide access to the binary you have to ensure that the binary is in your $PATH. Whilest you could add the project directories target folder to your $PATH, linking the binary in a folder that is already in your path is easier. Most of the Linux systems already have $HOME/.local/bin in their $PATH, so we can link the file into this folder.

ln -s $PWD/target/blueprint $HOME/.local/bin/blueprint

NOTE: You can replace the latter blueprint with a different file name, e.g. bp to make accessing BluePrint easier Now you should be able to type blueprint (or the name you choose) in your terminal to execute the binary

Using the BluePrintTranspiler

BluePrint Lisp flavour

This is the official BluePrint lisp flavour language documentation.

NOTE: You can use emacs to export the following examples into a seperate file (see example forlder for more)


As javascript is not a typed language, BluePrint isn't either. But BluePrint uses different type representations than javascript.


a logical data type that can have only the values true or false

- Wikipedia

In lisp, the values true and false are historically represented using t for true and nil for false.

;;; Booleans
(print t)     ; t - true
(print nil)   ; nil - false


The Transpiler itself seperated between Integers and Float


a number that can be written without a fractional component

- Wikipedia

(print 5)       ; an integer
(print 0)       ; an integer
(print -40)     ; an integer

a number with fractional component, represented in decimal form

(print 0.3)   ; float

Strings & chars

The Transpiler itselfs recognizes Strings and Chars as the same Text type But ideomatically lisp represents chars in single quotes

(print "Hello World")   ; a String
(print 'a')             ; a char

Lisp also features symbols, which are directly transformed into Javascript Symbols

(print 'Hallo)        ; a symbol


BluePrint has multiple different ways of declaring arrays, or lists as they are called in lisp.

Creating arrays the lispy-way

Lisp itself creates list the same way symbols are created, prefixing a round bracket set with a single quote

(print '(1 2 3)) ; creates an array/a list with the items 1, 2 and 3
Creating arrays using macros

Additionally BluePrint introduces macros, they are basically the same as functions, but have a trailing exclaimation mark, to symbolize that they are macros.


ARGUMENTS: the array items

RETURNS: the array

(print (arr! 1 2 3))    ; creates an array with the items 1, 2 and 3
Creating arrays using the fallback

The fallback conversion allows creating empty arrays using the raw javascript function


ARGUMENTS: same as the javascript new Array() function, at least one required - length of array [INTEGER]

RETURNS: an empty array with the specified length

(print (Array 5))       ; creates an empty array with a length of 5
Working with arrays
Selecting by index

The get! macro allows you to select items by index from an array


ARGUMENT 1: the array

ARGUMENT 2: the index to select (starts at 0) [INTEGER]

RETURNS: the item at the index

(print (get! '(1 2 3) 0))       ; returns the first item of the array
Binding array values

To be able to bind specific array values to variables, the multiple-values-bind function can be used.

FUNCTION: multiple-values-bind

ARGUMENT 1: a set of variable names to be bound

ARGUMENT 2: the array

RETURNS: nothing

(multiple-values-bind (a1 a2 a3) '(1 2 3)) ;;;; bind array index value to variable
(print a1 a2 a3)      ;;;; prints 1 2 3


Creating objects

Object creation follows similar rules as class creation: Specify a key with :<key> followd by the value.

NOTE: Both key and value have to be provided


ARGUMENT 1n: :<key>

ARGUMENT 2n: value

RETURNS: an object

(print (obj!
         :a "hello"
         :b "world"
Working with objects
Getting a key from an object

To get the value from a key in the object you can also use the get! macro


ARGUMENT 1: the object [OBJECT]

ARGUMENT 2: the key [STRING]

RETURNS: the value associated with the key

(print (get! (obj! :a 5) "a")) ;;;; use get! macro to read item
Binding object values

Similar to arrays, object values can also be bound to variables.

FUNCTION: multiple-values-bind

ARGUMENT 1: a set of keys to be bound as variables

ARGUMENT 2: the object

RETURNS: nothing

(object-values-bind (a4) (obj! :a4 5))    ;;;; map value of object to variable
(print a4)    ;;;; prints 5


Variables are created using the same keywords as Javascript normally would: Use const for contant variables and let for local, changing variables. (var is also implemented, but in most cases you should use let instead)

FUNCTION: const, var or let

ARGUMENT 1: variable name

ARGUMENT 2: value

RETURNS: nothing

(const a 5)     ; constant variable
(let b 4)       ; recommened way
(var c 5)       ; alternative to let

Chaning the value

NOTE: only variables declared using let or var can be changed, as they are NOT constant


ARGUMENT 1: variable name

ARGUMENT 2: new value

RETURNS: nothing

;;; changing a variable after initializing it
(set b 6)



BluePrint currently provides the three basic gates, which can be used to build every other gate.


Inverts a signal: If the input is thruthy the output will be falsey and vise versa.



RETURNS: opposite boolean of value

(print (not nil))       ; print t for true

The result will be true if either input1 or input2 are truthy.

NOTE: or will also return true if input1 and input2 are truthy

input 1 input 2 output
t t t
t nil t
nil t t
nil nil nil
(print (or t nil))      ; prints t

The result will only be true if input 1 and input 2

input 1 input 2 output
t t t
t nil nil
nil t nil
nil nil nil
(print (and t nil))      ; prints nil

Comparing values

BluePrint mostly uses the same symbols as javascript, with the exception of == (equal) being replaced by = and != (not equal) being replaced by /=.

FUNCTION: < (smaller), > (bigger), = (equal), /= (not equal), <= (smaller or equal), >= (bigger or equal)

ARGUMENT 1: value 1 [INTEGER] (= and /= also support [STRING])

ARGUMENT 2: value 2 [INTEGER] (= and /= also support [STRING])

RETURNS: true if condition is met

(print (< 5 4))     ; is 5 smaller than 4
(print (> 5 4))     ; is 5 bigger than 4
(print (= 5 4))     ; is 5 equal to 4
(print (/= 5 4))    ; is 5 not equal to 4
(print (<= 5 4))    ; is 5 smaller or equal to 4
(print (>= 5 4))    ; is 5 bigger or equal to 4


basic math

BluePrint uses the same math operators as javacsript with the addition of mod which works the same as %

FUNCTION: + (addition), - (subtraction), * (multiplication), / (division), % (and mod) (modulo)



RETURNS: result of said mathematical operation

(print (+ 1 2))         ; prints 3
(print (- 1 2))         ; prints -1
(print (* 1 2))         ; prints 2
(print (/ 1 2))         ; prints 0.5
(print (% 1 2))   ; modulo
(print (mod 1 2)) ; modulo
advanced functions
(print (min 3 5))   ;;;; print smallest value
(print (max 3 5))   ;;;; print largest number
(print (pow 2 5))   ;;;; 2^5
(print (sqrt 8))    ;;;; square root
(print (abs -5))    ;;;; converts negative numbers to positive numbers
(print (sin 40))
(print (cos 40))
(print (tan 40))
(print (asin 0.3))
(print (acos 0.3))
(print (atan 0.5))


Blueprint inherits its conditions from common lisp.

If Statement

Only executes code if condition is met. Similar to common lisp you can either use if or when.

FUNCTION: if or when

ARGUMENT 1: condition [bool]

OTHER ARGUMENT: code to be run if condition is met.

RETURNS: nothing

(if t
  (print "Hello"))
(when t
  (print "World"))

In addition to if and when there is an unless function, which functions as an inverted if and only runs code if the condition isn't met.

(unless nil
  (print "!!!"))

Multi conditional statements

Sometimes you do not only want to check if one condition is true, but test if other conditions are true as well and provide a solution if none of them are met.


ARGUMENTS: A set of a condition and code to be executed if the condition is met.

RETURNS: nothing

  (nil "wont be shown")
  (t (print "Fallback")))


There are multiple different types of loops, with every single one being useful in different scenarios.


Using the dotimes loop you can execute code a specified amount of times.

FUNCTION: dotimes

ARGUMENT 1: number of times the loop should run

OTHER ARGUMENTS: code to run

RETURNS: nothing

(dotimes 5
  (print "HI"))


The dowhile loop works exactly the same as the javascript while loop, running the code as long as the condition is met.

FUNCTION: dowhile

ARGUMENT 1: condition (specifies how long the loop is supposed to be run)

OTHER ARGUMENTS: code to be executed

RETURNS: nothing

(let counter 0)
(dowhile (< counter 3)
         (set counter (+ counter 1))
         (print "World")


The loop loop is the most advanced loops of all, with multiple different integrated features.


Although loop offers many complex methods, it can also be used as a simple white true loop. To exit the loop you have to use the break keyword.


ARGUMENTS: code to be run

RETURNS: nothing

(let cc t)
  (if (not cc) (break))
  (print "still true")
  (set cc nil)
Understanding do vs collect

When using advanced loop functions, you will notice that they provide a do and a collect keyword.

The do keyword, runs the loop normally without returning a value, whilst the collect keyword collects the returned value of the current iteration.

Loops using the collect keyword, return an array, containing all the values.

keyword returns
do nothing
collect array containing returned values

To understand the concept a little better you might want to check out the examples in the following sections

repeat for specified amount of time

Similar to dotimes, loop also provides a means to run code for a specified amount of times.


ARGUMENT 1: repeat

ARGUMENT 2: count - how often to run the loop

ARGUMENT 3: do or collect

OTHER ARGUMENTS: code to be run

RETURNS: nothing, when using do; list containing returned values, when using collect

NOTE: to access to index of the current iteration, loop repeat rebinds the i variable

(loop repeat 5 do
      (print "HI" i))

;;;;;; collecting number
(print (loop repeat 5 collect
             (return i)))
using for to iterate on a dataset

The most effective loop method might be the for loop, but as effective it might be, it might be hard to understand it at first.

using for to iterate over a range of numbers

The for loop allows you to iterate over a specified range of numbers. This can be seen as an analog to the repeat loop, but opposed to dotimes or repeat, the for from to loop allows you to specify a starting and an ending point.



ARGUMENT 2: variable name to assign to current value

ARGUMENT 3: from

ARGUMENT 4: starting point [number]


ARGUMENT 6: end point [number]

ARGUMENT 7: do or collect

OTHER ARGUMENTS: code to be run

RETURNS: nothing, when using do; an array containing the returned values, when using collect

(loop for x from 4 to 10 do
      (print x))
using for iterate over lists

Iterating over a list can be a useful feature, which is why BluePrint implements three different ways, of iterating over a list.

The following three for loop types can be summarized in the following diagram:



ARGUMENT 2: variable name

ARGUMENT 3: on, of or in

ARGUMENT 4: the list to iterate over [list]

ARGUMENT 5: do or collect

OTHER ARGUMENTS: the code to be run

RETURNS: nothing, when using do or a list containing the returned values, when using collect

Similar to common lisp, BluePrint contains an for-on loop, allowing to iterate over the other values of an array. Given an array a of length 3 containing the numbers 1, 2 and 3. The loop iteration would assign the following values:

iteration content
1 1,2,3
2 2,3
3 3
(loop for x on '(1 2 3) do
      (print x))
; print
; 1,2,3
; 2,3
; 3

BluePrint also implements two types of javacsript native for loops:

To iterate over the content of a list you can use the for-in loop.

(loop for x in '(1 2 3) do
      (print x))
; prints:
; 1
; 2
; 3

To iterate over the indexes(length) of an array you can use the for-of loop.

(loop for x of '(1 2 3) do
      (print x))
; prints:
; 0
; 1
; 2


Defining functions

Functions are basically reusable code components.

BluePrint uses the same defun function as lisp does.

NOTE: Functions arguments have to be listed in the rounded brackets. Currently setting default values is impossible - use unless where neccessary


ARGUMENT 1: set of arguments

OTHER ARGUMENTS: lisp commands to run

RETURNS: nothing

(defun sayhi (name)
  (print (format! "Hello, {}!" name)))    ; using format! macro to combine strings

Calling a function

Defined functions can be called the same way you would run any other lisp function.

FUNCTION: the function name

ARGUMENTS: the argument the defined function accepts

RETURNS: only returns something if the function returns a value

(sayhi "John")          ; prints "Hello, John!"

Returning from a function

Sometimes you need your function to return a value.

Returning a single value

To return a single value from your function you can use the return keyword.

FUNCTION: return

ARGUMENT: value to be returned

RETURNS: nothing -> ends execution, returning from function

(defun sum (a b)
  (return (+ a b)))
(let r (sum 4 3))
(print r)               ; prints 7
Returning multiple values

FUNCTION: values

ARGUMENTS: values to be returned

RETURNS: nothing -> ends executiong, returning from function

NOTE: values is supposed to be used in conjunction with multiple-values-bind to bind the returned items to new variables

(defun multi (a b c)
  (values a b c))
(multiple-values-bind (j k l) (multi 9 8 7))
(print j k l)

Lambda Functions

Are anonymous functions, that can be defined without giving a name. They are recommened as callbacks.

FUNCTION: lambda

ARGUMENT 1: set of arguments

OTHER ARGUMENTS: lisp code to run

RETURNS: a(n) (arrow) function

;;; e.g. dofile - see MODULES
(dofile "examples/modules.lisp" (lambda (m)
                                  (print m)))


As blueprint has to stay very close to the original javascript syntax, keeping close to the original lisp way of writing classes was impossible, as javascript does not allow for classes to be modified after creation.

Thus the blueprint classes follow the javascript syntax for classes.

NOTE: having private methods inside of a class is not possible at the moment

FUNCTION: defclass

ARGUMENT 1: class name

ARGUMENT 2: a list with the class to be extended (javascript only allows one parent class)

OTHER ARGUMENTS: methods for the given class


The class constructor is called once the class is created (using the new keyword or make-instance function).

It allows you to assign provided values to internal variables.

ARGUMENT 1: :constructor

ARGUMENT 2: constructor function to be run

(defclass Square ()
  (:constructor ((length)
                 (set this-length length)))
  ; ...

After creating your class you can construct it using the make-instance function.

FUNCTION: make-instance

ARGUMENT 1: symbol refering to class

OTHER ARGUMENTS: arguments passed to the class constructor

RETURNS: a constructed version / a member of the given class

(let mysquare
  (make-instance 'Square 40))


A getter is basically a function that is going to be called, when the property is accessed.

This allows you to run code before returning a value

ARGUMENT 1: :get

ARGUMENT 2: function/method/property name

ARGUMENT 3: the function to be run

(defclass Square ()
  ; ...
  (:get area (() (return (pow this-a 2))))
  ; ...
(print (s-area))


A setter can be seen as a function that is called to process the data assigned to a given value. It is also an amazing way to detect one a variable has been altered.

ARGUMENT 1: :set

ARGUMENT 2: function/method/property name

ARGUMENT 3: the function to be run

(defclass Square ()
  ; ...
  (:set area ((ar) (set this-a (sqrt ar))))
  ; ...
(print (s-a)) ; 40
(set s-area 16)
(print (s-a)) ; 4


Additionally you can also declare functions inside of classes, that can be called.

ARGUMENT 1: :func

ARGUMENT 2: function name

ARGUMENT 3: function (body) to be called

(defclass Dog ()
  ; ...
  (:func sayhi (()
               (print "bark"))))


Variables can be declared inside of a classes scope.

This might be useful if you need a specific value assigned to the variable, without wanting to write constructor code.

Like every other class method, variables can be accessed using the this keyword.

ARGUMENT 1: :dyn

ARGUMENT 2: variable name

ARGUMENT 3: initial value

(defclass Dog ()
  (:dyn max_age 5))


Excluding the constructor, every class method can be declared as static.

This allows you to access this specific method, without initialising the class.

ARGUMENT 0: :static

OTHER ARGUMENTS: see above, basically either declare a variable, setter, getter or function.

(defclass MyAPI ()
  (:static :dyn version 1))

(print MyAPI-version)


Macros are BluePrint functions that are not neccessarily native to javascript or lisp.

NOTE: Macros are indicated using a trailing exclaimation mark!

Formatting Strings

BluePrint makes formatting strings really easy, by providing a curly bracket replacement syntax. This means that every {} bracket pair is being replaced by its given replacement arguments.

The first bracket pair will be replaced with the second function argument, the second one with the third and so on…

FUNCTION: format!

ARGUMENT 1: template string

OTHER ARGUMENTS: replacement parts for the string

RETURNS: a new string based on the template with all {} replaced

(print (format! "Hello {}, you are {} years old" "Joe" 20)) ; prints: "Hello Joe you are 20 years old"

Creating arrays

As explained in the Types>Arrays section, arrays can be created using the arr! macro.


ARGUMENTS: array items

RETURNS: an array containing the provided items

(print (arr! 0 1 2 3 4))

Creating objects

As explained in the Types>Objects section, objects can be created using the obj! macro.

NOTE: Every key has to be followed by a value.

Keys are indicated by prefixing them with a colon.


ARGUMENT 1n: :<key>

ARGUMENT 2n: value

RETURNS: a new objects

(print (obj!
         :firstname "Jane"
         :lastname "Doe"
         :id 0
         :age 25))

Getting items from an array on an object

To select values from an array or object, you can use the get! macro.


ARGUMENT 1: array or object

ARGUMENT 2: index or key name

RETURNS: value at given index or key

(print (get! (arr! 0 2 3) 2))       ;;;; get third item from array
(print (get! (obj! :d 3 :b 2) "d")) ;;;; get value with key d from object

Fallback conversion

As manually adding every javascript command, from every possible javascript module would be an impossible task, BluePrint has a fallback conversion.

FUNCTION: javascript function name

ARGUMENTS: arguments normally provided to javascript

RETURNS: What the javascript function would return

How it works

The following will be run for every command (<function-name> ...<arguments>) where no other conversion system is found.

  1. replace every - with . in the function name
  2. extract all arguments and resolve them
  3. put all the resolved arguments inside of round brackets ()
  4. prefix the round bracket with the function name

Basic Example

(alert "Hello World") ; -> alert("Hello World")

Using initialisers

This fallback also allows access to constructor functions. e.g. create an array with a length of 5:

(print (Array 5))

Advanced Example

This fallback also allows for use of prototyped functions. Ideally most of them would be manually implemented, to allow for nesting, but implementing every single functions take a lot of time and this way is recommened until then.

(let f (Array 5)) ; initializes an empty array with length of 5
(f-fill 1)        ; fills the array with 1s
(set f (f-map (lambda (c i) (return (+ i c)))))   ;;;; maps over the array increasing the value by one
(print f)         ; prints the new value of f


Importing a module

import functions the same as the javascript keyword and imports a module with a given name from a given path


FUNCTION: import

ARGUMENT 1: name of object to import

ARGUMENT 2: from

ARGUMENT 3: module path (or name when working with npm) [STRING]

RETURNS: nothing

(import express from "express")

Allows you to rename the imported function.

FUNCTION: import

ARGUMENT 1: name of object to import


ARGUMENT 3: new name

ARGUMENT 4: from

ARGUMENT 5: module path (or name when working with npm) [STRING]

RETURNS: nothing

(import * as Feather from "feather-icons")
Importing asynchrounously

In some cases you might not want to import a file, once execution begins. Normally you would use import in conjunction with a promise in javascript, but in BluePrint this has been shortened and is now done using the dofile command.

dofile takes two arguments: the module path and the callback function.

NOTE: the loaded module data will be provided to the callback as an argument

FUNCTION: dofile

ARGUMENT 1: path to module (or module name when using npm) [STRING]

ARGUMENT 2: function to call once module is loaded [FUNCTION(data)] (Recommended: lambda functions)

RETURNS: nothing

(dofile "./examples/demo.js" (lambda (dt)
                               (print "loaded")))

CommonJS Modules

NOTE: Though this conversion works without any issues, the generated code will only work in NodeJS CommonJS modules are handled using fallback conversion.

FUNCTION: require

ARGUMENT: path to module (or module name when using npm) [STRING]

RETURNS: an object or the same as require in nodejs would

(let a
  (require "examples/arrays.js"))

Exporting from a module

You export from a module the same way you would do in javacsript


FUNCTION: export

ARGUMENT 1: name of object to export

RETURNS: nothing

(export sayhi)
Default Exports

Same as the export default javascript expression.

FUNCTION: export-default

ARGUMENT 1: name of object to export

RETURNS: nothing

(export-default sayhi)


Multiline expressions

progn allows you to write multiple lines of javascript code in one line.


ARGUMENTS: more functions

RETURNS: nothing

  (print 1)
  (print 2)
  (print 3))

Templated Commands

As of BluePrint v2 the transpiler has a template feature. Using .toml files, you can define your own simple conversions.

[<command_name>] len = <argument_count> template = <template_string>

NOTE: you have to provide the EXACT argument count your function accepts

NOTE: the function name is argument 0, but should not be included in the argument count

Arguments passed to the function can be accessed using { n }

accessor definition example
{ 0 } function name replace
{ 1 } argument 1 target string
{ 2 } argument 2 match
{ 3 } argument 3 replace
{ n } nth argument
len = 3
template = "{ 1 }.split({ 2 }).join({ 3 })"

To load your functions you pass the toml-template file path to blueprint using the -t path/to/file.toml option. This allows BluePrint to transpile your custom commands.

FUNCTION: your function name

ARGUMENTS: the arguments your function should take

RETURNS: the formatted template string you provided

(replace "A real string" "real" "fake") ; returns a new string, where "real" has been replaced with "fake"

Rust API Documentation