Bleh
Paradigm(s) | Functional |
---|---|
Designed by | Ashli Katt |
Appeared in | 2024 |
Computational class | Unknown |
Reference implementation | Unimplemented |
File extension(s) | .bleh |
Bleh is a functional language with some imperative aspects to make up for the lack of first-class functions. Bitstrings are the only datatype in Bleh. Functions calls and pattern matching are the only form of flow control.
Within source code, //
is used for line comments, and /* */
is used for block comments.
Whitespace is generally not significant, only used to separate identifiers (a b
is distinct from ab
)
Functions
Bleh scripts are made up of 0 or more functions. Functions may be "invoked" by passing in 0 or more arguments, and will either return a value or crash the program.
Functions have a name and have 0 or more "branches."
A branch contains 0 or more patterns, and a single expression.
Patterns look at values and will determine if they meet certain criteria, such as starting with a 1 or having length 5. Patterns may "bind" variables for use in expressions.
Expressions construct new values and may use variables defined in their branch's patterns.
Both of these have their own sections later on in this document.
When a function is invoked, it will attempt to match the supplied arguments to each branch's patterns in-order, if all patterns in a branch match, the corresponding expression is evaluated and returned. If no branches' patterns match, the program crashes.
Functions are declared by writing their name, 0 or more branches, and then a semicolon.
Branches are written as a colon, then zero or more comma-separated patterns, then an equals sign and expression.
Examples:
// Function with 0 patterns, this always crashes. name; // Function with 1 pattern name2: pattern = expression; // Function with multiple patterns name3 : pattern, pattern = expression // 2 Patterns : pattern = expression // 1 Pattern := expression // 0 Patterns ; // Note that ":=" is just ": =", don't confuse it for special syntax!
Valid function names are made of 1 or more uppercase ASCII letters, lowercase ASCII letters, ASCII digits, or underscores. Function names cannot be made of only 1
s and 0
s, and cannot be a single underscore.
Expressions
An expression evaluates to a bitstring value. The following are all valid expressions.
1011
- Literal bitstring. Made up of 1 or more1
s and0
s."abc"
- Typical string rules, escaped with backslashes. Evaluates to a bitstring based on the UTF-8 bytes of the string.'
may also be used as a delimiter.x
- References value of of variablex
. Variables covered in the patterns chapter.
[...]
- Any number of comma-separated expressions within brackets will evaluate to all elements concatenated together.1101 == [11, 01]
.{...}
- 1 or more semicolon-separated expressions may be put within braces, and will evaluate each expression linearly, returning the value of the final element.{}
is a syntax error.
x(...)
- Invokes functionx
, Any number of comma-separated expressions may be put into the parenthesis as arguments. Evaluates to whatever the function returns.x
will never be treated as a a variable.x y
- Shorthand forx(y)
.x
will never be treated as a a variable.
Notes:
Trailing commas/semicolons are allowed.
Code is always evaluated lazily, if a function doesn't need to evaluate an argument to match a pattern, then it won't! This is important to keep in mind for {}
blocks.
Here is a large example showing how the above may interact:
// Let's assume x and all functions are defined in this context.
someFunction{
theOutputOfThisIsIgnored[
11010011,
[],
{
otherFunction(x)
}
];
"This \"string\" (bitstring) is completely unnecessary";
101
}
Patterns
Patterns are used to match a value. If a pattern matches is value, it may "bind" any number of variables.
The following are valid patterns:
- Literal bitstring / string expressions. Will match a value if they are exactly equal.
_
- Matches any value.
x
- Ifx
isn't bound, match any bitstring and bind it tox
. Ifx
is already bound, match the value bound tox
exactly.
x[...]
- Destructure a bitstring into single-bit-long bitstrings, then match those against 0 or more comma-separated sub-patterns. Binds the whole bitstring to optional variablex
, if present.
x..
- Matches any number of values and store the concatenated value into optional variablex
, if present. Two..
s cannot appear in the same pattern "level," ie:[a.., ..]
is ambiguous and thus disallowed, however[..], ..
is allowed, etc.
Notes:
Variables follow the same naming scheme as functions. If a variable has been "bound," it may be used within an expression in the same scope as the pattern, or later on in the same pattern. The leftmost instance of a variable will bind first.
Example patterns:
Pattern | Input | Matches? | Bindings |
---|---|---|---|
1 |
1 |
Yes | |
0 |
1 |
No | |
"a" |
01100001 |
Yes | |
"a" |
1100001 |
No | |
x |
1 |
Yes | x=1 |
[a, b] |
11 |
Yes | a=1, b=1 |
[_, b] |
10 |
Yes | b=0 |
[a, b] |
101 |
No | |
[a, b..] |
101 |
Yes | a=1, b=01 |
x[_, ..] |
1011 |
Yes | x=1011 |
x[_, ..] |
[] |
No | |
x[a, b..] |
1011 |
Yes | a=1, b=011, x=1011 |
[x, x] |
00 |
Yes | x=0 |
[x, x] |
11 |
Yes | x=1 |
[x, x] |
10 |
No | |
x, 1 |
1, 1 |
Yes | x=1 |
x, 1 |
1, 0 |
No | |
x.. |
1, 0, 10, 1 |
Yes | x=10101 |
IO
A function named main
will be invoked with 0 arguments when the program begins execution. The program will terminate when this function finishes evaluating.
An IO module (see below for modules) should be provided by the compiler/interpreter located at std::io
.
The following functions should be included in io
:
debug
- Prints input bitstring(s) to stdout as[]
or a series of1
s and0
s, concatenates all 0+ arguments. Returns empty bitstring.
print
- Appends input bitstring to stdout as UTF-8 bytes, concatenates all 0+ arguments. Returns empty bitstring.
next
- Returns the next character from stdin as UTF-8. Multi-byte characters will be converted into one bitstring. Output length will always be a multiple of 8, including 0 for EOF. Accepts 0 arguments.
hasNext
- Returns 1 if there are more characters in the input buffer, otherwise 0. Accepts 0 arguments.
Module Format
Bleh has a defined format for creating and importing modules. It is up to the implementation whether or not to include this feature, however. (A web-based script interpreter may choose to ignore it entirely, for example)
Every single Bleh file (.bleh
) is considered to be a module made up of public and private functions.
A function is private by default, but can be made public by prefixing their definition with ::
.
// Public function that calls a private function ::public := private(); // Private function that returns a constant private := [];
If we assume the above code is inside of example.bleh
, we could import it into another Bleh module and then write example::public()
to run the function.
We could not, however, use example::private()
because it is private.
Modules may be imported by using the ^
symbol. It follows similar syntax to a function with multiple branches.
^ : path : path = name : path = name ;
Multiple import statements are allowed, and they can have any number of branches. Each branch contains a path to another Bleh module, and optionally a rename.
Paths are normally local to the project directory defined by the compiler. ::
separates directories.
Paths beginning with ::
will be relative to the current file's parent directory.
An example import header could look like this:
^ : ::this_module // Import this module into itself. (Assuming this is this_module.bleh) : ::sibling // Import sibling.bleh from the same directory as this : ::A::other // Import A/other.bleh inside of this module's directory : lib = YB // Import lib.bleh from the project root ;
This would allow us to call functions in sibling.bleh
and other.bleh
by writing sibling::function(..)
and other::function(..)
, respectively.
We can also call functions from this module by writing this_module::function(..)
(This will only allow public functions, however). We can also call functions in lib.bleh
by writing YB::function(..)
(Note that we renamed it with =
).
Note: The main
function will only be automatically called for the entrance file, other files' main
will not be automatically invoked.
Examples
Hello, World!
^: std::io; main := io::print "Hello, World!";
Cat
^: std::io; // Check if there's output to read and pass into printLoop main := printLoop io::hasNext(); // If input is 0 (no output left to read), end. Otherwise print the next input byte and loop. printLoop : 0 = 0 : 1 = { io::print io::next(); // Print the next input byte printLoop io::hasNext(); // Call printLoop again };
Truth Machine
^: std::io; // Get next char from input and pass it to choice. main := choice io::next(); // If input is exactly UTF-8 zero, call print0, otherwise call spin. choice : "0" = print0() : .. = spin() ; // ^, the .. pattern here matches everything, including any number of arguments. // Just print 0 and return print0 := io::debug 0; // Infinite loop of printing 1 spin := { io::debug 1; spin() // Tail-recursion should be optimized so this doesn't overflow call stack };
Booleans
// Small little library for boolean operations on 0 and 1. // Passing anything other than exactly 0 and 1 to these functions will crash. NOT : 0 = 1 : [_] = 0 ; // Short-circuiting because _ won't evaluate argument AND : 0, _ = 0 : [_], [x] = x ; // Short-circuiting, ditto OR : 1, _ = 1 : [_], [x] = x ; XOR : [x], [x] = 0 : [_], [_] = 1 ;
Simple Numbers
// Sum 3 bits, return the main sum bitSum : [a], [b], [c] = XOR(a, XOR(b, c)); // Sum 3 bits, return the carry bitCarry: [a], [b], [c] = OR(AND(a, b), AND(c, XOR(a, b))); sum // Two inputs, re-call but with carry argument = 0 : A, B = sum(A, B, 0) // Pop last bit of both numbers and calculate new digit for that position. // Recursively sum the rest, set carry argument accordingly. : [R1.., D1], [R2.., D2], [C] = [sum(R1, R2, bitCarry(D1, D2)), bitSum(D1, D2)] // One number is empty, simplifies math to just A + Carry : [R.., D], [], [C] = [sum(R, [], AND(D, C)), XOR(D, C)] : [], [R.., D], [C] = [sum(R, [], AND(D, C)), XOR(D, C)] // Both empty numbers, add carry bit if necessary : [], [], 1 = 1 : [], [], 0 = [] ;
Computability
It is suspected, but not yet proven, that Bleh is Turing complete.