Vixen

From Esolang
Jump to navigation Jump to search
Vixen
Designed by User:Corbin
Appeared in 2025
Computational class Turing-complete
Reference implementation included in this article!
Influenced by Cello, COLA, Dylan, E, Erlang, execline, Monte, Nix, s6, Self, Smalltix, Woosh, zope.interface

Vixen is a calling convention for Unix systems which idealizes directories as objects. It is similar to Smalltix[1] but reifies objects as clonable prototypes as in Self[2] or COLA languages like Pepsi[3].

The name "Vixen" is a pun on "VAXen" as the plural of wikipedia:VAX, with "-en" both meaning "more than one machine" and also short for "E" and "Nix", the two main innovations included since Smalltalk and Unix.

Objects

The Vixen connection is similar to Smalltix but does not have classes. There is only one sort of object, the directory. A directory's entries each represent a slot with the name of the entry providing the verb for that slot. If an entry is executable then it is a method. There is only one calling convention; to call method $METH on object $OBJ with arguments $@, execute:

$OBJ/$METH $OBJ $@

Delegation is achieved via symlinks within a directory which refer to other objects. Self-style prototype-based inheritance is achieved by symlinking one directory, the parent object, to the parent* slot of another. During message delivery, if there is no slot corresponding to the called selector but there is a parent* entry then the message will be delivered to the parent object on behalf of the child object. That is, suppose that our previous example is extended with a symlink to a parent object $PAR, and further suppose that the selector is not present in $OBJ but it is present in $PAR. Then we execute:

$PAR/$METH $OBJ $@

The slot name parent* is inherited from Self and is unlikely to collide with other names due to the trailing asterisk. In general, any delegating symlink should be named with a trailing asterisk, as if references were in a distinct namespace from instance methods.

Core

The core of Vixen is reproduced here using execline. Let $V be some directory. To animate it, we need a method for calling methods on objects. It needs to discern whether a slot denotes a method by checking whether it is executable. The following method $V/call: will work:

#!/usr/bin/env -S execlineb -s2
importas -iS V
backtick -I -E method { ${V}/lookup: $1 $2 }
ifte {
    ifte { $method $1 $@ } { cat $method }
    eltest -x $method
} { exit 1 }
eltest $? -eq 0

This method must delegate the actual lookup of the method on the recipient. This lookup method needs to be recursive so that it can walk the parents. The following method $V/lookup: will work:

#!/usr/bin/env -S execlineb -s2
ifte { echo ${1}/${2} } {
    ifte { $0 ${1}/parent* $2 } { exit 1 }
    eltest -e ${1}/parent*
}
eltest -e ${1}/${2}

The interested reader should start their journey by adding printing for objects, messages, and errors.

Cloning

As in Self, the fundamental unit of object creation is invocation of clone: methods; once cloned, an object can be customized by standard Unix techniques. To address the concerns of Kell[4] and Jakubovic about allocation, let $Alloc be a directory which we idealize as an object and will pass to clone: as a parameter. First, assuming that we have already written a method $Alloc/allocate which allocates a new object, we initialize that object by creating a parent* symlink within it. The following method $V/clone: will work:

#!/usr/bin/env -S execlineb -s2
importas -iS V
backtick -E obj { ${V}/call: $2 allocate }
backtick -E target { realpath $1 }
foreground { ln -s $target ${obj}/parent* }
echo $obj

Let $Alloc/base name a reasonable temporary directory, perhaps $TMPDIR, which will be the base directory where allocated objects are located. The following method $Alloc/allocate will work:

#!/usr/bin/env -S execlineb -s1
importas -iS V
backtick -E basePath { ${V}/lookup: $1 base }
backtick -E base { cat $basePath }
mktemp -d -p $base obj.XXXXX

Asynchrony

As in E, a message can either be delivered in the foreground or the background. The execline background command is sufficient to implement the functionality, although we will want to reify the background process as its own object. First, the following method $V/send: will work:

#!/usr/bin/env -S execlineb -s0
importas -iS V
importas -iS Process
background { ${V}/call: $@ }
importas -iu pid !
${V}/call: $Process fromPID: $pid

This method relies on an auxiliary $Process object which represents a Unix process by PID. The simplest object which will work is a clone of $V which has a slot $Process/allocator referring to an allocator like $Alloc from the previous section. On top of such an object, the following method $Process/fromPID: will work:

#!/usr/bin/env -S execlineb -s2
importas -iS V
backtick -E allocator { ${V}/call: ${1} allocator }
backtick -E obj { ${V}/call: $1 clone: $allocator }
foreground { echo $obj }
redirfd -w -n 1 ${obj}/pid
echo $2

Expression language

The Vixen calling convention can support Smalltalk-style chained calls and cascaded calls. The following Raku grammar recognizes a simple grammar for blocks, resembling the grammars of Self or GNU Smalltalk:

grammar Vixen {
    token id       { <[A..Za..z*]>+ <![:]> }
    token selector { <[A..Za..z*]>+ \: }
    token string   { \" <[A..Za..z*]>* \" }
    token param    { \: <id> }
    token ass      { \:\= }
    proto rule lit             {*}
          rule lit:sym<block>  { '[' <param>* '|' <exprs> ']' }
          rule lit:sym<paren>  { '(' <call> ')' }
          rule lit:sym<string> { <string> }
          rule lit:sym<name>   { <id> }
    rule chain { <id>* % <ws> }
    proto rule unary {*}
          rule unary:sym<chain> { <lit> <chain> }
    rule keyword { <selector> <unary> }
    proto rule message {*}
          rule message:sym<key>  { <keyword>+ }
    rule messages { <message>* % ';' }
    proto rule call {*}
          rule call:sym<cascade> { <unary> <messages> }
    proto rule assign {*}
          rule assign:sym<name> { <id> <ass> <call> }
          rule assign:sym<call> { <call> }
    rule statement { <assign> '.' }
    proto rule exprs {*}
          rule exprs:sym<rv>  { <statement>* '^' <call> }
          rule exprs:sym<nil> { <statement>* <call> }
    rule TOP { '[' <param>* '|' <exprs> ']' }
}

This grammar must be equipped with actions in order to serve as a compiler. Once prepared, it can be used to compile Vixen expressions to execline scripts. For a short example, here is the classic Smalltalk method V/yourself, implemented as the expression [|^self]:

#!/usr/bin/env -S execlineb -s1
# [|^self]
importas -iS V
importas -iS Nil
define self $1
echo $self

The reader is encouraged to imagine compiler actions which optimize away the define or the reference to Nil.

Complexity class

Vixen orchestrates Turing-complete components and as such is also Turing-complete. In particular, it is immediate from folklore of pointer-chasing and symlinks that:

Theorem (Undecidability of Halting for Vixen). It is undecidable whether a Unix system interpreted as a Vixen object graph will respond to a given message.

Similarly, we cannot know whether a component will ever perform an arbitrary effect at an arbitrary point.

Theorem (Undecidability of Printing for Vixen). It is undecidable whether delivering a single message to a Vixen object graph will produce any output.

References

  1. J. Jakubovic, 2025. The Unix executable as a Smalltalk method. Onward! 2025. https://programmingmadecomplicated.wordpress.com/wp-content/uploads/2025/10/onward25-jakubovic.pdf
  2. D. Ungar & R. B. Smith, 1987. Self: the power of simplicity. OOPSLA 87, 1987. Lisp and Symbolic Computation, 4, 3, 1991. https://bibliography.selflanguage.org/_static/self-power.pdf
  3. I. Piumarta, 2006. Pepsi: not quite the real thing. https://www.piumarta.com/software/cola/idst-20070918/doc/pepsi.html.in
  4. S. Kell, 2015. Towards a dynamic object model within Unix processes. Onward! 2015. https://dl.acm.org/doi/10.1145/2814228.2814238