~fd/cane-lang

A funky programming language made in 24(ish) hours
Make web playground reference spec
switch to flakes; fix flatten action
Remove unneeded add in docs

refs

main
browse  log 

clone

read-only
https://git.sr.ht/~fd/cane-lang
read/write
git@git.sr.ht:~fd/cane-lang

You can also use your local clone with git send-email.

#The Cane Programming Language

A funky programming language made in 24(ish) hours

#Examples!

; Simple "Hello, world!" program ~

(`hello, world' println)
;
It's pretty simple to define a variable
You need: the data, and a string as a name.
~

((1 2 3 4 add) `myvarname' defvar)
(myvarname println)
(myvarname println)
(myvarname println)
(myvarname println)

; All variables are immutable! You can replace the variable, but you can't edit it. ~

((1 2 3 4/3 add) `myvarname' defvar)
(myvarname println)
((1 2 3 4 5 add) `myvarname' defvar)
(myvarname println)
;
actions, or functions, are just strings that are executed. arguments passed are available
through the `args' variable
~

([(args add)] `myaction' defact) ; define an action called `myaction' ~
(1 2 3 4 myaction print) ; call myaction with arguments 1 2 3 4 ~
; A simple program that prints the numbers from 1 to 100 ~
; Creates a variable to store the counter, then recursively increments it ~
; This does mean that loops have a finite size before stack overflow occurs ~

(1 `counter' defvar)
(
	[(counter 100 
		[
			(counter println)
			(1 counter add `counter' defvar)
			(body)
		]
	`' lte)]
	`body' defact
)
(body)
; Prints the numbers from 1 to 1000000 without recursion ~

(1 `count' defvar)
(
	[
		(count println)
		(count 1 add `count' defvar)
	]
	1000000 repeat
)

#Building

It's very simple. Install Rust and compile it just like with every other Rust project:

$ cargo build --release
$ target/release/cane FILE

#WASM Version

Go into the cane-wasm directory and run:

wasm-pack build --target web

Open the index.html file in your favourite browser.

#Spec

Well, there really isn't one. I'll just tell you what you can and can't expect, as well as the currently-implemented tokens.

Cane is a reverse-polish-notation language. This means that the data comes before the "trigger" for the action that is executed. Some actions consume the data, some change it, some add to it, and some do nothing to it.

#Basic Actions

Name Args Notes
print any Prints the data as a string. Does not consume data
println any Prints the data as a string with a newline appended. Does not consume data
add any Adds the data together. Not recursive. Consumes data. Based on type of first item. Adds second item to first, then third item to that, then so on.
drop any Deletes all previous data
duplicate (Data..., Number) Duplicates the data N times. N must be a non-negative integer. Does not consume data (it makes more of it)
flatten Data If lists are nested, move them all to the top level. No lists will remain.

#Data and Casts

Data in Cane can be of the following:

  1. Action (function)
  2. List of Data
  3. String
  4. Number (infinite precision rational number)
#Lists

Lists contain data, including other lists.

Lists can be joined into strings with a separator:

Name Args Notes
join (Data..., String) Joins the data into a string with delimiter specified by the last parameter.
#Unwrapping

To take the contents of an inner list and move it to the parent list, use an unwrap: ?.

Example:

(1 2 3 (4 5)?)

That will bring 4 5 into the outer list:

(1 2 3 4 5)
#Strings

Strings begin with a ` and end with a '. See also special action notation.

Strings can be escaped. Currently the following escapes are implemented:

  • \n: newline
  • \r: carriage return
  • \': single quote
  • \\: single backslash

Strings can be split into a list inplace: the string will be "exploded". More strings will be returned.

Name Args Notes
split (String, String) Splits the first string by the delimiter in the second string.
#Numbers

All numbers are infinite precision rational numbers. They are represented as base-10 irrational fractions.

Examples:

(1 2 8/2 -5439053/123129)
Name Args Notes
mult (Number...) Multiplies the numbers in order: (1 2 3 mult) is 1 * 2 * 3 = 6
sub (Number...) Subtracts the numbers in order: (1 2 3 sub) is 1 - 2 - 3 = -4
div (Number...) Divides the numbers in order: (1 2 3 div) is (1/2)/3 = 1/6
neg Number Negates the previous number: (1 neg) is (-1)
floor Number Rounds down the previous number: (99/98 floor) is (1)
ceil Number Rounds up the previous number: (99/98 ceil) is (2)
abs Number Finds the distance from zero of previous number: (-99/98 abs) is (99/98)

Note that add is not number-specific so it is documented in basic actions.

#Casts

These can be converted between each other with casts. Each consumes the data preceding and replaces it with the new data.

Name From To Notes
tostring any String Concats list to string
tonumber any Number Returns list length. Tries to convert string to number and panics on fail.
tolist any List Explodes string into list of characters. Wraps number in list.

#Actions and Variables

Variables are saved data with a String key. Actions are saved strings with a String key. Actions are executed by making a new interpreter and running the code in the string.

Name Args Notes
defvar (Data, String) The String will be the name of the variable
defact (String, String) The String will be the name of the action
#Special Action Notation

Because actions take strings, nesting them can make code very messy. Surround the string with [ and ]. No escaping will be done.

Before:

(1 `counter' defvar)
(
	`(counter 100 `(counter `\n\\\' print) (1 counter add `counter\\\' defvar) (body)\' `\' lte)' 
	`body' defact
)
(body)

After:

(1 `counter' defvar)
(
	[(counter 100 [(counter `\n' print) (1 counter add `counter' defvar) (body)] 0 lte)]
	`body' defact
)
(body)

#Control Flow

Control flow works similarly to actions. It will evaluate the data, and then execute a string based on the result.

  • All control flow operations take the following arguments: (Data..., String, String).
  • The control flow will act on the data.
  • The first string is executed for the "true" condition. The second string will be executed for the "false" condition.
  • The data from the execution will be returned.
  • The data to check will be consumed.
  • If types don't match, false will automatically be returned.
Name Args Notes
eq (Data..., String, String) All data must be of the same type and value
lt (Data..., String, String) Checks if the data is in ascending order (no duplicates)
lte (Data..., String, String) Checks if the data is in ascending order (duplicates allowed)
gt (Data..., String, String) Checks if the data is in descending order (no duplicates)
gte (Data..., String, String) Checks if the data is in descending order (duplicates allowed)
#Special Control Flow String Shorthand

When returning a string, it would have to be nested like so:

(1 2 `(`ascending\')' ``descending\'' lt print)

or by using special action notation:

(1 2 [(`ascending')] [(`descending')] lt print)

instead, the string can be passed directly:

(1 2 `ascending' `descending' lt print)

This has the interesting property making any program with invalid tokens valid because it will be saved into an internal string buffer, then it will exit.

#Loops

Loops are similar to control flow. It will execute the string N (or infinity) times.

Name Args Notes
repeat String, Number Executes the string N times. N must be a non-negative integer.
forever String Executes the string forever.

Example:

; Prints "hi" 100 times ~

(
	[
		(`hi' println)
	]
	100 repeat
)