This is a walkthrough of the Felise programming language. Felise is loosely situated as a concatenative programming language. It is an interpreted language (it requires the felise interpreter on the system to run programs) in its present form.
This walkthrough will be done by example. If you would like to follow along in the repl, you can clone the repository here. The repl features tab completion for all procedures and has a few quality of life options. Feel free to skip around as your level of experience dictates. If you are fully new to stack based/concatenative programming, maybe try reading a bit of the wikipedia article on the subject first. With that said, let's get to it…
# This is a comment
| This is also a comment, but it is
used for docstrings (procedure
documentation). It is multiline.
More on this later. |
# Add/View values to/on the stack
# ===============================
5 1.3 "Hello"
stackdump # Take a look at the stack
drop # Throw away TOS ("Hello")
println # Pop and print TOS w/ a newline
print # Pop and print TOS w/ no newline
stackdepth println #=> 0
# Let's do some "math" on the stack
# =================================
2 3 + println #=> 5
2 3.2 + println #=> 5.2; INT coerces to FLOAT
2.25 3.1 + println #=> 5.35
"Hello" "World" + println #=> HelloWorld
"Hello" 55 + println #=> Hello55
2 3 - println #=> -1
2.25 3.1 - println #=> -0.8500000000000001
"Hello World" "o" - println #=> HellWrld
"H3ll0 W0rld" 0 - println #=> H3ll Wrld ; 0 is INT & coerces to STRING
[ "Hi" "Hello" "Hi" ] "Hi" - println #=> [ "Hello" ]
[ 1 2.25 "Hi" ] 2.25 - println #=> [ 1 "Hi" ]
2 3 / println #=> 0
2.25 3.1 / println #=> 0.7258064516129032
"Hello World" " " / println #=> [ "Hello" "World" ] ; split into LIST
2 3 * println #=> 6
2.25 3.1 * println #=> 6.9750000000000005
"Hello World" 2 * println #=> Hello WorldHello World
[ "Hello" "World" ] "__" * println #=> "Hello__World" ; Join LIST to STRING
# Let's work with variables
# =========================
# Declare the type then the keyword
# then the variable name
INT var! myInt
myInt println #=> 0
# The new var takes on the "zero" value of the type
# using 'myVar' above adds the value of myVar to TOS
# Set the variable as follows:
5 set! myInt
myInt println #=> 5
# You cannot set a variable to a value of a type that
# does not match the declared type:
"Nope" set! myInt #=> ERROR!!!!!
clearstack #=> Clears the stack
# Note: variables are local to the scope they are
# created in, and are accessible to child scopes
# Let's work boolean logic
# ========================
# Tip: false, 0, [], and {} are all falsy
5 3 > println #=> true
5 3 < println #=> false
5 3 >= println #=> true
5 3 <= println #=> false
5 3 = println #=> false
5 3 != println #=> true
5 3 = not println #=> true
true false and #=> false
true false or #=> true
5 type FLOAT = #=> false
5 type INT = #=> false
5 3 > if
"Bigger" println
else
"Smaller" println
end #=> Bigger
# Let's cast some values
# ======================
# The available types are:
# INT, FLOAT, STRING, BOOL, TYPE, LIST, DICT
5 FLOAT cast println # Our INT (5) is now a FLOAT (5.0)
"5" FLOAT cast println # Our STRING ("5") is now a FLOAT (5.0)
"5" INT cast println # Our STRING ("5") is now an INT (5)
"5" BOOL cast println # Our STRING ("5") is now a BOOL (true)
"" BOOL cast println # Our STRING ("") is now a BOOL (false)
0 BOOL cast println # Our INT (0) is now a FLOAT (false)
5 STRING cast println # Our INT (5) is now a STRING ("5")
# Let's use LISTs and DICTs
# =========================
# Get values (A LIST can hold any type, including LIST)
[ "Hello" "world" ] 1 <- println #=> Hello
[ "Hello" "world" ] 2 <- println #=> world
[ "Hello" "world" ] -1 <- println #=> world
# It works mostly the same with a DICT
# Note that DICT keys must be STRINGs, but values
# can be of any type (including a DICT)
{
"age" 33
"weight" 150
"hair" "brown"
"smoker" false
} "age" <- println #=> 33
# Append a new item...
[ "Hello" "world" ] false => println #=> [ "Hello" "world" false ]
# For a DICT just set a new key as a value...
{ } "happy" true -> println #=> { "happy" true }
# Change the value of an existing index/key...
[ "Hello" "world" false ] 3 "!" -> println #=> [ "Hello" "world" "!" ]
{ "age" 33 "weight" 150 } "age" 34 println #=> { "age" 34 "weight" 150 }
# There is a shorthand to reference LIST/DICT items
{ "test" [ "Hi" "Bye" ] } svar! d # Create a DICT with a LIST value and store it to var `d`
d("test")(1) println #=> Hi
d("test"(1)) println #=> Hi
# Either of the above reference syntaxes will work (nested or sequential call)
# Let's write a procedure
# =======================
# We'll iterate over building a simple procedure
# Let's call it `square`. It will take a number
# (INT or FLOAT) and multiply it times itself
proc square
dup * # dup will add a copy of the top stack item to the stack
end
# That works, but let's make sure we don't
# get weird side effects (squaring a non-number)
proc square
dup type INT = over type FLOAT = or if
dup *
else
"`square` expected an INT, but found "
swap type + # Move the STRING under the top stack
# value, get the type, and append it to the STRING
throw # then throw an error with a value of the STRING
end
end
# Let's add a docstring so people will know how to use it
# It is standard to include what it takes from the stack,
# if it forward reads a symbol, and what it puts on the stack.
# It is also good to leave a note that describes what happens.
proc square
| Stack: INT/FLOAT
Read :
Push : INT/FLOAT
Notes: Takes an INT or FLOAT and pushes the square of the same type to TOS |
dup type INT = over type FLOAT = or if
dup *
else
"`square` expected an INT, but found "
swap type + # Move the STRING under the top stack
# value, get the type, and append it to the STRING
throw # then throw an error with a value of the STRING
end
end
5 square println #=> 25
# We can read the docstring, too...
docstring! square println
#=>`square`
# Stack: INT/FLOAT
# Read :
# Push : INT/FLOAT
# Notes: Takes an INT or FLOAT and pushes the square of the same type to TOS |
# Reading Internal Docs
# =====================
# To see all procedures the system knows about:
words println
# The above will add a STRING to TOS that lists every proc name
# that the system knows. When you create a proc, it will show up
# in the STRING produced by `words`
# Now that you know the proc names, you can use `docstring!` to
# see what they do and how to use them:
docstring! net-get println #=>
# `net-get`
# Stack: STRING
# Read :
# Push : DICT
# Notes: Takes a gemini, gopher, or http(s) url STRING from TOS.
# A response DICT will be left on the stack, key "success"
# will be a BOOL declaring whether or not the request was
# successful, key "status" will be the status code of the
# response (or -1 if there is no status code), key "body"
# will be the response body. If the response was successful
# key "body" will be the actual body of the response, if
# it was not successful it will be an error message.
# Try importing a library and seeing the listing in `words` change
"strings" import
words println
# Now you can see things like `string-to-lower`, which was not
# available before. Note: `import` consumes a string with
# a standard builtin package ("math", "paths", "stack",
# and "strings"), but you can also pass it a string that
# represents a filepath to a felise file to import
docstring! string-to-lower println
That is enough to get you going with the basics of felise. There are a lot more complicated things present in the language that allow meta-programming, functional programming, interacting with a *nix environment, etc. Plus a lot of built in procs that can do neat things. Play around and you will find cool things to do. The README.md that comes in the source code repository has information about a number of these features, as well as some language "gotchas", installation information, and other good info.