Infilisp
| Paradigm(s) | functional |
|---|---|
| Designed by | User:Orisphera |
| Appeared in | 2024 |
| Type system | dynamic, duck |
| Computational class | Turing-complete |
| Reference implementation | Unimplemented |
| Influenced by | Lisp |
Infilisp is a language by Orisphera. It has an incomplete specification given below; the way to complete it may influence how procedurally generated programs work.
History
Before Infilisp, Orisphera had another idea based on Lisp. In it, everything would be expressed through lists. Also, the last argument of the built-in functions wouldn't be added to the expression as an element; instead, its contents would be added to it. (For example, (car (quote a) quote (b)) would evaluate to (a b). Note that the language wouldn't actually use words, and that code, passed to the interpreter, would actually be treated as (()()) and evaluate to (()). The words would have to be replaced with strings of parentheses according to the specification.)
Later, Orisphera came up with two ideas for procedural generation in computer games to implement in some future. One was procedurally generating structures. In it, smaller structures would be combined into larger ones, which would then be combined into even larger ones, and so on. The same structures could be repeated several times in a larger one and across different ones. This idea is not very important to Infilisp, although it can be combined with the other one.
The other one is procedurally generated code. For it, Orisphera decided to create their own language. Later, they decided that it would be a Lisp derivative. (They also considered Nix and Haskell, but decided to stick to Lisp.) Then, they decided to make the last argument of functions unpacked, like in the older idea. This idea evolved into Infilisp.
Working
In Infilisp, the program is internally represented as an infinite binary tree. The nodes contain some information, but any finite change under which it remains correct doesn't matter. The tree is dynamically generated. The language standard library provides some basic instructions that can be used to write Infilisp code to generate it. The node types should follow the below specification:
The types should have the following methods (as they're called in the (incomplete) reference implementation): get car cdr apply eval to_bool to_ll. Except for to_bool to_ll, they should return Infilisp objects. The to_bool method should return a boolean, and to_int should return an integer. (The exact type may depend on the use case.) The apply method also takes an object as an argument. Any types added to the library must obey the following rules (note: in the reference implementation, objects are InfilispOP, but types should inherit from InfilispObNorm or InfilispObEOU and implement methods that take the reference to the object as the (first) argument):
- These rules use the notion of equivalent objects. This notion is defined axiomatically. A library designer should make it so that, assuming that existing types allow for a definition that works, they should also allow for one after adding their types.
- Every object a is equivalent to a.
- If an object a is equivalent to objects b and c, b is equivalent to c.
- Methods of objects (in the above implementation —
InfilispOP) that are equivalent to each other should give objects that are also equivalent to each other.- In case of
to_boolandto_ll, they should give the same result instead. - Similarly,
applyof the same object applied to objects that are equivalent to each other should also give objects that are also equivalent to each other.
- In case of
- For any object
self:self.get()should be equivalent toself;self.apply(other)should be equivalent toself.eval().apply(other)for anyother;self.eval(other)should be equivalent toself.car().apply(self.cdr().eval());self.to_ll()should be equal toself.cdr().to_ll() * 2ifself.car.to_bool()is false and 1 more otherwise;self.to_bool()should be equal toself.car.to_bool() || self.cdr.to_bool().
- If
self.car()is equivalent toother.car()andself.cdr()is equivalent toother.cdr(), thenselfandothermust be equivalent.
The to_bool() method may return the true value for all objects not equivalent to a certain one; whether it does is up to whoever completes the specification.
The implementation should preferably provide two intermediate classes to inherit from. In one (InfilispObEOU in the reference implementation), all operations except for get should be defined, and in the other one (InfilispObNorm in the reference implementation), all operations except for car and cdr should be defined.
It is advised to avoid infinite loops as much as possible. However, avoiding them completely is, unfortunately, impossible.
Types
Infilisp includes the folowing types:
- Integer (the exact type is implementation-defined, but it must include 0 and -1):
cargives -1 for odd numbers and 0 for even numbers;cdrgives the number divided by 2, rounding down (3 -> 1, -3 -> -2);applygivesother.car()for negative numbers andother.cdr()for non-negative ones;evalgives -1 for negative numbers and 0 for non-negative ones;to_boolgives the true value iff it isn't 0;to_llgives the number.
- List (
std::shared_ptr<std::vector<InfilispOP>>) with a starting index and a "next" object:carreturns the item at the starting index;cdrreturns the list with the starting index increased by 1, but if that increased index is the length, it returns the "next" object.
Other types should be specified in the complete specification.
Syntax
There are l-expressions and r-expressions. L-expressions can be assigned to to create an updated namespace, and r-expressions represent objects.
There are the following l-expressions:
- An identifier like in normal PLs denotes a variable value;
- Identifiers starting with
c, ending withr, and otherwise consisting ofaandd(such ascaddr) are equivalent tox: `([-1 for each a and 0 for each d] x); - A library identifier (see below) is imported from its library;
- A number denotes that number;
- One or more expressions enclosed in
()define a list with 0 as the starting index and 0 as the "next" object; - One or more expressions followed by
.and enclosed in()define a list with 0 as the starting index and the specified "next" object; - An l-expression followed by
:and then an r-expression define a lambda that, when called, will assign the argument to the l-expression and return the r-expression with it; - An l-expression followed by
=and then two r-expressions returns the second r-expression with the first one assigned to the l-expression; - An r-expression preceded by
'denotes an object for whichevalreturns the specified object; - An r-expression preceded by
`denotes the result of usingevalon the specified object.
There are the following r-expressions:
- An identifier like in normal PLs denotes a variable that's defined to the assigned value when assigned;
- One or more expressions enclosed in
()assigns thecar cdar cddar ...of the assigned value to its parts; - One or more expressions followed by
.does the same, but assignsc[as many d's as there are parts before the .]rto the part after the..
Built-in identifiers are:
quote, when applied to an object, returns an object thateval's to it;eval, when applied to an object, returns the output of that object'seval;cons, when applied to an object, returns the object thatcar's to that object'scar().eval()andcdr's to that object'scdr().eval();bool, when applied to an object, returns -1 for ones that have trueto_bool()and 0 for others.
Library identifiers are constructed by joining identifiers using ::. The library is a tree, and the identifier is a path in it. Using localfs::"..." where ... is a file path allows one to import from the local file system.
External links
- Reference implementation (It's unknown if it will actually be used; it may be re-written in Lua. Also, some applications may require using
mpz_classinstead oflong long. Also, some implementation may use caching for better performance.)