31 KiB
- About BluePrint
- Getting Started
- Using the BluePrintTranspiler
- BluePrint Lisp flavour
- Rust API Documentation
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
BluePrint lisp to javascript transpiler
What?
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.
Why?
BluePrint (back then called SchemeScript) was split from cracker, my lisp interpreter (still WIP), after I watched the retroedge.tech 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.
Features
- 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.
Contributing
Check out the CONTRIBUTING.org file
Getting Started
NOTE: We currently do not provide prebuild binaries
Requirements
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 https://codeberg.org/comcloudway/blueprint
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
./target/blueprint
[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)
Types
As javascript is not a typed language, BluePrint isn't either. But BluePrint uses different type representations than javascript.
Booleans
a logical data type that can have only the values true or false
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
Numbers
The Transpiler itself seperated between Integers and Float
Integers
a number that can be written without a fractional component
(print 5) ; an integer
(print 0) ; an integer
(print -40) ; an integer
Floats
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
Symbols
Lisp also features symbols, which are directly transformed into Javascript Symbols
(print 'Hallo) ; a symbol
Arrays
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.
FUNCTION:
arr!
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
FUNCTION:
Array
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
FUNCTION:
get!
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
Objects
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
FUNCTION: obj!
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
FUNCTION: get!
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
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
orlet
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
FUNCTION: set
ARGUMENT 1: variable name
ARGUMENT 2: new value
RETURNS: nothing
;;; changing a variable after initializing it
(set b 6)
Logic
Gates
BluePrint currently provides the three basic gates, which can be used to build every other gate.
Not
Inverts a signal: If the input is thruthy the output will be falsey and vise versa.
FUNCTION:
not
ARGUMENT: value
RETURNS: opposite boolean of value
(print (not nil)) ; print t for true
Or
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
And
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
Calculations
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),%
(andmod
) (modulo)ARGUMENT 1: value 1 [INTEGER]
ARGUMENT 2: value 2 [INTEGER]
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
geometry
(print (sin 40))
(print (cos 40))
(print (tan 40))
(print (asin 0.3))
(print (acos 0.3))
(print (atan 0.5))
Conditions
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.
FUNCTION: cond
ARGUMENTS: A set of a condition and code to be executed if the condition is met.
RETURNS: nothing
(cond
(nil "wont be shown")
(t (print "Fallback")))
Loops
There are multiple different types of loops, with every single one being useful in different scenarios.
dotimes
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"))
dowhile
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")
)
loop
The loop
loop is the most advanced loops of all,
with multiple different integrated features.
basic
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.
FUNCTION: loop
ARGUMENTS: code to be run
RETURNS: nothing
(let cc t)
(loop
(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.
FUNCTION: loop
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.
FUNCTION: loop
ARGUMENT 1: for
ARGUMENT 2: variable name to assign to current value
ARGUMENT 3: from
ARGUMENT 4: starting point [number]
ARGUMENT 5: to
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:
FUNCTION: loop
ARGUMENT 1: for
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
Functions
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
FUNCTION:
defun
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)))
Classes
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
Constructor
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))
Getter
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))
Setter
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
Functions
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
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))
Static
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
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.
FUNCTION:
arr!
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.
FUNCTION:
obj!
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.
FUNCTION:
get!
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.
- replace every
-
with.
in the function name - extract all arguments and resolve them
- put all the resolved arguments inside of round brackets ()
- 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
Modules
Importing a module
import functions the same as the javascript keyword and imports a module with a given name from a given path
Basic
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")
Advanced
Allows you to rename the imported function.
FUNCTION:
import
ARGUMENT 1: name of object to import
ARGUMENT 2:
as
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
Basic
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)
Advanced
Multiline expressions
progn allows you to write multiple lines of javascript code in one line.
FUNCTION:
progn
ARGUMENTS: more functions
RETURNS:
nothing
(progn
(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 |
[replace]
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"