Welcome to TeXCHR (spoken as "tech-cher")! To run the example you just need plain, good-old TeX. Run:
tex example.texThis is based on the Python FreeCHR implementation by Sascha Rechenberger.
I've written this more as a joke than a real piece of good TeX software, you could do a lot better and several macros are now over-generalized (most do not need arguments, as we could just lock a name like \constraints for each program).
The main file is chr.tex, which includes everything desired to get chr for your (plain) TeX project.
Feel free to improve it or to criticize it, I'm open to suggestions.
Allows to use FreeCHR in plain TeX. Every program must be wrapped in \chr{name}{program} (results can be printed directly or stored using \gdef/\xdef).
Basic \Rule{name}{kept-heads,...}{removed-heads,...}{guard}{body}, \Compose{rules,...}, and \Run{constraints,...} with FreeCHR semantics.1
Within the heads, you can use \c to access the current constraint! For example, if your constraints are just numbers, you can use {\ifnum\c>2} to only allow numbers that are greater than 2 (in general, your heads have to each expand to or behave equivalently to either \iftrue or \iffalse).
Within the guard you can access all matched constraints using \c as a list (i.e., access them with \c{0}, \c{1}, ...) based on the order of your heads (additionally, you can name them using \def\n{\c{0}}, ...). The guard has to expand to a conditional too.
Similarly, in your body, you can access all matched constraints as \c{i} with i being their 0-based index.
That order is the FreeCHR one: kept heads first, removed heads after. So with \Rule{r}{{k1},{k2}}{{r1}}{guard}{body} the guard and the body see \c{0} for k1, \c{1} for k2 and \c{2} for r1. The values matched by the removed heads leave the store, and what the body produces is put in front of the rest (b(*matching) + constraints1 in the Python version).
All heads, the guard, and the body can have side effects, given that they still expand to a conditional/update the constraint list chr@constraint in the case of the body (for this, you can use \body and \ebody, see below).
Additionally, there are helper functions like
\trueand\falsefor constant true and false guards or heads\log{text...}to output information during the execution. You have to call\enablelogso that logging works.\body{constraints,...}and\ebody{constraints,...}(expands with\edef) can be used in thebodyargument of\Rule(and\rule) to add new constraints more easily (to the main listchr@constraints). They are best used at the tail of the body.\LimitCycles{number}can be used to halt the execution after a maximum ofnumbercycles (e.g. if your Rules have no guaranteed fixpoint). If you hit the limit, you get a warning in the log.\makelist{list-name}{elements,...}to construct lists (using them as\name{index}to access elements, setter and modifications functions are currently not exposed and live under the\chr@...namespace), see thelist.tex.\listequal{list-name1}{list-name2}to compare lists element-wise, expands to\iftrue/\iffalserespectively\permute{list-name}{length}gives you every ordered selection oflengthpairwise distinct entries of the list, in the order of Python'sitertools.permutations(list, length)- no entry twice, no selection twice, and nothing at all iflengthis bigger than the list. For each selection, this expands the\chr@@outputmacro, which has access to thelist-name(its firstlengthentries hold the current selection) and a shortened\chr@list@colllist-presentation (which can be used to copy the list with\makelist{list-name}{\chr@list@coll}).\makeatletterand\makeatotherhelp you to access all internal macros (which use the\chr@namespace).- Corresponding to
\Rule,\Compose, and\Run, there are lowercase variants\rule,\compose, and\runwhich take the names of lists (created with\makelistinstead of the lists directly.
You want fibonacci? You can have fibonacci!
\chr{fib test}{%
\LimitCycles{25}
\Compose{
\Rule{main}%
{} % empty kept head
{{\true}} % removed head
{\true}
{% poor man's tuple
\tuple{\c{0}}%
\add{\fst}{\snd}%
% ebody expands its argument to replace
\ebody{\snd:\res}%
}
}
\Run{{0:1}}
}To work with tuples you need some helpers:
\def\tuplehelper#1:#2\@nil{\def\fst{#1}\def\snd{#2}}
\def\tuple#1{\edef\@tmp{#1}\expandafter\tuplehelper\@tmp\@nil}
\def\add#1#2{\chr@tempcount=#1\relax\advance\chr@tempcount by #2\relax\edef\res{\the\chr@tempcount}}See the example.tex for a full example or the playground.tex for more. testfiles/ has a few small programs whose results are checked against the Python version, run them with l3build check (plain TeX, both tex and pdftex).
Please note, that integer arithmetic is limited by TeX.
This follows the very abstract operational semantics of FreeCHR, which is what the Python version implements too:
- a rule fires if the store holds a distinct value for every head pattern and the guard likes them together,
\Composetries its rules in order and stops at the first one that changes the store,\Runapplies the composed solver until the store stops changing.
There is no propagation history. The very abstract semantics has none (you only get one in the refined semantics), so a propagation rule with a non-empty body will never reach a fixpoint here, same as in the Python version. That is what \LimitCycles is for.
The store is a sequence and not a multiset, and the fixpoint check compares it element-wise (the Python version compares lists, so same thing). Removing a matched value removes its first occurrence.
Footnotes
-
For the time being,
composeis limited to once-per-program (I was too lazy to implement proper nesting). ↩