
\documentclass[12pt]{article}
\usepackage[T2A,OT1]{fontenc}
\usepackage[default]{cantarell}
\usepackage[a4paper, top=20mm, bottom=20mm, left=20mm, right=20mm]{geometry}
\usepackage[utf8]{inputenc}
\usepackage[russian, english]{babel}
\usepackage{tabu}
\usepackage{hyperref}
\usepackage{parskip}
\usepackage{graphicx}
\usepackage{tabularx}
\usepackage{float}
\floatstyle{boxed}
\restylefloat{figure}
\usepackage{setspace}
\onehalfspacing
\author{Artyom Bologov}
\date{\today}
\title{Advanced Self-Aware ed(1)}
\makeatletter
\def\endenv{\expandafter\end\expandafter{\@currenvir}}
\makeatother
\begin{document}
\maketitle

\includegraphics[width=\textwidth,height=\textheight,keepaspectratio]{./assets/advanced-ed.png}

I won’t shut up about ed(1), don’t even ask.
Because it’s a good text editor and IDE.
\href{run:sed-ed}{And a perfect text computation playground (section interactive)}.
Bare regex acting on lines of text.
What else could one want?

Oh, right.
Turing completeness.
That’s a hard one.
It’s a text editor (I say with an indecypherable smile.)
Not supposed to be Turing-complete, right?

Wrong.
ed(1) is Turing complete.
Because:

\begin{enumerate}\item It can fall through to the shell, and shell is (awkwardly) Turing-complete.
\item But shell is boring. ed(1) can call itself instead!
\item And \verb|w|-rite commands to its own standard input.
\item Thus allowing buffer data to turn into executable commands.
\end{enumerate}

So meta.
Infinite potential.

\section*{Conditionals and Early Returns} \label{cond}

Now wait, we know that Turing-completeness requires branching.
\verb|if|-s and \verb|else|-s.
There’s no such thing in ed(1).
Not in the conventional sense.
But! there’s \verb|g|-lobal command.

\verb|g| goes through every line, matching it with a regex.
If the regex does match, it performs the provided actions.
A sequence of them, in fact.
And moves on to the next matching line.

So \verb|g| is closer to loops or maps from conventional programming language.
But still, there’s conditionality in it, so why not?

In case you need something more… conventional than this weird regex mapping.
You can do early escapes/returns.
If you consider every ed(1) script a function of its own, it starts making sense.
Doing an action until the data is ready.
Two techniques there:

\begin{itemize}\item Using \verb|g/existing/q| to quit when there’s a terminating condition/pattern/data.
\item Using \verb|s/nonexistent/| to break out of the script if the necessary data is missing.
\end{itemize}

The perfect ed(1) script terminates when reaching the desired state of text.
Because that’s easy enough to encode.
And it plays well with recursion and self-calls.
Say what?

\section*{Self Calls} \label{self-calls}

One task I had that seemingly didn’t fit ed(1) was file inclusion.
\href{run:this-post-is-ed}{I use ed(1) to build my website}.
Naturally, I need a way to include/expand templates.
Usually represented by separate files with variables in them.
I used an imaginary HTML tag \verb|<include "filename">|.
Here’s a script that did the dirty work of including the necessary files from these tags:

\begin{figure}[h!]\begin{verbatim}
g/<include "*\([^">]*[^"> -]\{3\}\)"*\/*>/s//&\
H\
/<include/d\
-1r \3\
wq\
/
g/<include "*\([^">]*\)"*\/*>/d\
.,+4w !ed %
E
\end{verbatim}\caption{A hack for arbitrary file inclusion using ed(1) self-call}\end{figure}

The logic is:

\begin{enumerate}\item Take lines with these \verb|include| instructions.
\item Insert commands processing them into the buffer.
  Including the \verb|r| command using the template file name captured by regex.
\item Go through each instruction and call ed(1) with these commands.
\item Read the toplevel file back in after the instructions are done.
\end{enumerate}

So the pattern is clear: process the anchor line/instruction into a set of commands.
Make these commands act on the arguments substituted from the instruction.
And call ed(1) on that set of commands.

Same technique is used in my shell execution instructions.
Basically tags of a form \verb|<exec "ls -l">|.
Execute a shell command and read its output back into the buffer.

\begin{figure}[h!]\begin{verbatim}
g/<exec "*([^">]*[^"> -]{3})"*/*>/s//&\
H\
/<exec/d\
-1r !\3\
wq\
/
g/<exec "*([^">]*)"*/*>/d\
.,+4w !ed %
E
\end{verbatim}\caption{Shell command execution via recursive call to ed(1), itself calling shell}\end{figure}

Same technique, except it’s \verb|r !\3| (read output of command X) instead of \verb|r \3| (read file X.)

\section*{Iter... Recursion!} \label{recursion}

Now that we learned to call ed(1) from inside ed(1)…
Why not call is some more?
Many times.
Maybe even infinite number of times!
Yes, I’m talking recursion.

Recursive scripts combine both conditionals and self-calls.
Conditionals are particularly important: they allow to bail out from recursion.
Instead of recurring indefinitely.

Here’s an example script that modifies itself until it has the necessary number of \verb|ɩ| (iota.)

\begin{figure}[h!]\begin{verbatim}
H
$g/ɩ\{70\}/q
$s/$/ɩ
w
,w !cat % | ed -s %
Q
ɩ
\end{verbatim}\caption{Useless yet exemplary recursive script: add iotas one by one until there are seventy}\end{figure}

I know, not the most practical script.
But hey, ed(1) was powerful enough for me to have no need for recursion!
Yet.

\paragraph{There’s iteration too!} \begin{quote}
All the kudos goes to a cool anonymous person for this hack:
You can create N lines and act on them to repeat some modification on the buffer:

\begin{figure}[h!]\begin{verbatim}
a
1
2
3
data
data
data
.
1,3g/.*/4,$s/.*/&&/n
Q
!# Results in:
!# 6	datadata
!# 6	datadatadatadata
!# 6	datadatadatadatadatadatadatadata
\end{verbatim}\caption{Iteration}\end{figure}
\end{quote}

\section*{ed Is (Turing) Complete} \label{complete}

So yeah, ed(1) is a powerful enough programming system, supporting

\begin{itemize}\item positive conditionals
\item negative conditionals
\item early returns
\item data mapping
\item self-calls
\item (shell calls, but that’s boring)
\item recursion and iteration
\end{itemize}

I mean, even BASIC can’t do some of these.
And they used BASIC in eighties for Real Programming™.
They might’ve used ed(1) and end up better for that.

Don’t commit their mistakes.
Use ed(1).

\par\noindent\rule{\textwidth}{0.4pt}

P.S.
\href{https://nixwindows.wordpress.com/2018/03/13/ed1-is-turing-complete}{I know that ed(1) Turing-completeness was already proven}.
The difference of the proof there and my post is that I’m actually using ed for practical things.
Most of the hacks here are the ones I made for my nefarious needs.
Enjoy.

\par\noindent\rule{\textwidth}{0.4pt}

P.P.S. Just for fun, here’s a super meta script:

\begin{figure}[h!]\begin{verbatim}
!echo "HELLO" > hello.txt
E hello.txt
p
s/.*/p\
s|.*|p\\\
s;.*;p\\\\\\\
Q\\\\\\\
;\\\
,w !ed %\\\
Q\\\
|\
,w !ed %\
Q\
/
,w !ed %
Q
\end{verbatim}\caption{Run it with ed < script.ed}\end{figure}

\par\noindent\rule{\textwidth}{0.4pt}

P.P.P.S. There’s a whole treasure trove in
\href{https://rosettacode.org/wiki/Category:Ed}{my ed(1) entries on RosettaCode},
but I leave reverse-engineering them to a more capable person.
Have a good time watching me dissolve in the madness!

\par\noindent\rule{\textwidth}{0.4pt}
Copyright 2022-2025 Artyom Bologov (aartaka).
Any and all opinions listed here are my own and not representative of my employers; future, past and present.

\end{document}
