Bleh

From Esolang
Jump to navigation Jump to search
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 1s and 0s, 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 more 1s and 0s.
  • "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 variable x. 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 function x, 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 for x(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.
  • x - If x isn't bound, match any bitstring and bind it to x. If x is already bound, match the value bound to x 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 variable x, if present.
  • x.. - Matches any number of values and store the concatenated value into optional variable x, 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 of 1s and 0s, 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.