Can

From Esolang
Jump to navigation Jump to search
can
Paradigm(s) Object-oriented, Functional
Designed by Nxe
Appeared in 2021
Type system static
Memory system variable-based
Computational class Turing complete
Reference implementation Unimplemented
File extension(s) .can, .can.txt
Note that can is typically lowercased except, often, at the start of a sentence.

Can is a statically types esolang created by Nxe in December 2021. Can is a binary based language with all commands based on boolean algebra, with a few added extras. The name is an "alternative" to the shortened bin (from binary). This esolang was created after using the same notation for notes in a computer systems class to display boolean algebra in code, and as such is fairly simple to read.

Syntax

Note that this section uses regex syntax within definitions, with descriptors placed inside angled brackets (<,>)

The syntax for can is fairly simple

Can syntax
Command Description
<< Bit shift left
>> Bit shift right
& Bit-wise and
| Bit-wise or
Bit-wise xor1
~ Bit-wise not
:= Variable assignment
-> Return statement
=> Output given value
<= Input value
^ Run if value is zero
1This is character code 0x25ca

You can also use rounded brackets (( and )) as you would expect, to enclose expressions that would otherwise be evaluated in a different way

Comments are sections that begin with a / and end in a newline. This does not have to be the first character, but any characters afterwards are treated as comments.

Variables

Because can is designed around binary arithmetic, all variables have a set size in bits ranging from 1 to 64 bits. The syntax for defining a variable is as follows

   <size of variable in bits> <variable name> := <value>

The variable name must match the regex string [\w\d_]+ and the size of the variable cannot be larger than 64 bits. The value can be written in any base using the following prefixes

Value base prefixes
Prefix Base
None Base 10
0d Base 10
0b Binary
0x Hexidecimal
0o Octal

All variables in can are unsigned integers. It is up to the end user if they want to extend this to use signed integers (whatever system of signing you want to use) and/or floating point numbers. For example, a 12 bit variable example with the initial value of 0x20d would be

   12 example := 0o1015

Local variable names

Variables are all local, with no way to access variables from outside of their scope. This means that variables with the same name do not necessarily always refer to the same variable.

Functions

Functions are defined in a similar way to variables with the addition of arguments and one small alteration to the value type

   <function return format> <function name>((<arg bit size> <argument identifier>(, <arg bit size> <argument identifier>)?)?) := {
       <function suite>
   }

Argument identifiers must match the same regex string as for variable identifiers.

The function return type can be a single value, in which case the bit size of the value is required. However, you can also return several values from a function. This comes from the inspiration for the language. Binary gates can have several outputs, and thus so can functions in can. If multiple values are returned, the bit length has to be specified in order. Returning several values is done on multiple lines, with each line representing one return value. For example, a simple half adder would return a sum bit and a carry bit. In our case we'll return them in that order. The sum bit is the xor of the two inputs, and the carry is the two inputs anded together. This means we can write a full adder as follows

(1, 1) half_adder(1 A, 1 B) := {
    -> A ◊ B
    -> A & B
}

If we changed the return type to be (2, 1) then the sum bit would become a sum two-bits. A value like this can't be stored as a single variable, but must be unpacked into separate variables. These values also can't be passed into an output function. A good example of this is the 4-bit adder in the examples section

IO

IO in can is dealt with in a very binary way. All inputs are read in UTF-8 and only the 8-bit character code is made available to the program. Output is dealt with slightly differently. To output something the syntax is as follows

   => <output type> <value>

The output type here indicates if the value being output is a UTF-8 character c or a numerical value v. If the output type is a character the only the first 8 bits are used. If the value is longer than 8 bits the value is "truncated" (Or anded with 0xff). Some useful UTF-8 codes are

Value base prefixes
Hex code(s) Character(s)
0x0041-0x005a A-Z
0x0061-0x007a a-z
0x002b +
0x002d -

Examples

Hello World!

=> c 0x48
=> c 0x65
=> c 0x6c
=> c 0x6c
=> c 0x6f
=> c 0x20
=> c 0x57
=> c 0x6f
=> c 0x72
=> c 0x6c
=> c 0x64
=> c 0x21

Adding

This is a 4 bit adder. I will not write out any larger adder by hand
(1, 1) half_add(1 A, 1 B) := {
    -> A ◊ B  / Sum bit
    -> A & B  / Carry bit
}

(1, 1) full_add(1 A, 1 B, 1 C) := {
    1 S1, 1 C1 := half_add(A, B)  / Sum A and B
    1 S, 1 C2 := half_add(S1, C)  / Sum the sum of A and B with the carry bit
    1 C := C1 | C2                / Set the final carry
    -> S                          / Return the sum
    -> C                          / Return the carry
}

4 add(4 a, 4 b) := {
    4 s, 1 c := half_add(a & 1, b & 1)    / Initialise the output variable to the first sum bit and save the carry bit
    1 s_, c := full_add(a & 2, b & 2, c)  / Get sum and carry of the second bit
    s := s | (s_ << 1)                    / Add the second sum bit to the total sum
    s_, c := full_add(a & 4, b & 4, c)    / Continue the pattern for the 3rd and 4th bits
    s := s | (s_ << 2)
    s_, c := full_add(a & 8, b & 8, c)
    s := s | (s_ << 2)
    -> s
}

Interpreter

A can interpreter is currently being written in Rust. Bear with me on that though