Foreach

From Esolang
Jump to navigation Jump to search
Foreach
Paradigm(s) Imperative
Designed by Ashli Katt
Appeared in 2023
Computational class Unknown
Reference implementation [1]
File extension(s) .forx

About

Foreach is an imperative esolang where the only data-type is arrays, and the only flow control is for-each loops.

Overview

A Foreach program consists of any number of variable, constant, and function declarations; they may be declared like so:

varName = value;
constName := value;
funcName parameterName <statement>

varName, constName, funcName, etc are all identifiers. An identifier in Foreach can contain any characters except for whitespace or the following: []{};.

A value is either a variable name, an array literal, or function call. Arrays are written in the form [x] where x is 0 or more values separated by ;s. The following are all valid values:

a
[]
[a]
[[];[]]
[a;[];[[];[b]];[c;[]]]
[ab]
[a;b]

Functions

Functions are written in the form funcName paramName statement. All functions have exactly one parameter. Both funcName and paramName are identifiers representing the name of the function or the name of the parameter, respectively. statement is any valid statement in Foreach. Valid statements are as follows:

  • Variable declaration/assignment (varName = value;)
  • Constant declaration (constName := value;)
  • Foreach (constName := value => <statement>, taking another statement)
  • Function return (-> value;)
  • 0 or more statements surrounded in {}

A a := b => statement will work like a usual for-each loop, running the statement once for each element in b, with a assigned to that element for the iteration.

A function return works as expected, immediately exiting the function and returning that value. If a function ends without encountering a return, [] is returned by default.

Statements inside of {} will be executed linearly, as expected.

Functions may be called with the form funcName value, where value is supplied as the parameter.

IO Format

Input and output are done using four functions:

  • io.next x Will return the next bit. x is ignored.
  • io.bits x Will return an array of all input bits, does not affect io.next. x is ignored.
  • io.out x Will push x to the output buffer.
  • io.debug x Will directly print x to the console in array format.

Input and output are both strings. Given an input, say "abc", it must be converted into an array representation. First, each character will be converted into its UTF-16 binary representation, with the LSD on the left. Thus it becomes the bitstring 0000000001100001 0000000001100010 0000000001100011. 1 is treated as [[]] while 0 is treated as [].

  • io.next[] would return [], then [[]], then [[]], and so on.
  • io.bits would return [[];[];[];[];[];[];[];[];[[]];[[]];[]...]

io.out [[]] would push a 1 to the output buffer. The output buffer is a similar bitstring that, upon reaching a multiple of 16 bits, will reset and print as a character. [] is treated as 0, anything else is 1.

Notes

  • There is no way to continue/break outside of a for loop except for early-returning.
  • All arrays are immutable.
  • A for loop will create a clone of the array and loop over it; changing a variable will not affect the loop.
  • An identifier is any string of non-whitespace characters that don't contain []{};, so long as it isn't equal to one of the keywords =, :=, =>, or ->.
  • There is no variable shadowing. When a variable is set, it will only create a new variable if it doesn't exist in an outer scope already.
  • Variable names have higher precedence than function names.
  • There is no order of operations, as the only "operation" is calling a function, which only has one parameter and is prefix.
  • There is no grouping construct like ().

Examples

Cat program:

main _ { // Declare main function, ignore input (always []).
  bit := io.bits[] => { // Loop over bits of io.bits[].
    io.out bit; // Print each bit in order
  }
}

Explanation: The input bits are collected into an array via io.bits[] ([] is a dummy input because the function takes none). The bits are then iterated over and printed out one-by-one using io.out.


Hello, World!:

0 := []; 1 := [0]; main _ i := [
0;0;0;0;0;0;0;0;0;1;0;0;1;0;0;0; // H
0;0;0;0;0;0;0;0;0;1;1;0;0;1;0;1; // e
0;0;0;0;0;0;0;0;0;1;1;0;1;1;0;0; // l
0;0;0;0;0;0;0;0;0;1;1;0;1;1;0;0; // l
0;0;0;0;0;0;0;0;0;1;1;0;1;1;1;1; // o
0;0;0;0;0;0;0;0;0;0;1;0;1;1;0;0; // ,
0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0; //  
0;0;0;0;0;0;0;0;0;1;0;1;0;1;1;1; // W
0;0;0;0;0;0;0;0;0;1;1;0;1;1;1;1; // o
0;0;0;0;0;0;0;0;0;1;1;1;0;0;1;0; // r
0;0;0;0;0;0;0;0;0;1;1;0;1;1;0;0; // l
0;0;0;0;0;0;0;0;0;1;1;0;0;1;0;0; // d
0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;1  // !
] => io.out i;

Simple boolean definition

false :=   [];
true  := [[]];

not inp {
  v := inp => {
    -> false;
  }
  -> true;
}

Explanation: The not function will loop over the input array, and will immediately return false inside the loop. In other words, if the length of the input array is greater than 0, return false. Otherwise return true.

Extended definition:

false :=   [];
true  := [[]];

// True -> False, False -> True
! inp { v := inp => -> false; -> true; }

// True if input array contains only truthy values.
&& inp {
  v := inp => _ := ! v => -> false;
  -> true;
}

// True if input array contains at least 1 truthy value.
|| inp v := inp => _ := v => -> true;

// True if number of truthy values in input array is odd 
^ inp {
  out = false;
  v := inp => _ := v => out = ! out;
  -> out;
}


Simple array static indexing: (Assumed booleans are implemented)

(0) arr i := arr => -> i;

(1) arr {
  skip = false;
  i := arr => {
    _ := skip => -> i;
    skip = true;
  }
}


// Can be extended with skip2, skip3, etc vars. 
// A general-purpose solution may be made if numbers are implemented.


Peano number implementation, including general indexing (with all above):

++ num -> [num];
-- num i := num => -> i;

0 := []; 1 := ++ 0; 2 := ++ 1; 3 := ++ 2; 4 := ++ 3; 5 := ++ 4; 6 := ++ 5; 7 := ++ 6; 8 := ++ 7; 9 := ++ 8;

// Call like this: index[arr; i];
index inp {
  arr := (0) inp;
  index = (1) inp;
  v := arr => {
    _ := ! index => -> v;
    index = -- index;
  }
}

Turing Completeness

It is suspected that Foreach is turing-complete, but has not been proved as such.