b5 == 0 General b5 is a macro processor that takes one or more character streams (input) and produces another stream from that by replacing all occurrences of macros in the input by their defined expansions. b5 is aimed for preprocessing text, programming, producing files or streams with lots of repetition and/or redundant logic. In addition to these, b5 is meant to be a relatively easily understandable toy that implements the lambda calculus. 1 Input The input of b5 consists of two parts: rules and a data source stream. Rules are macro definitions with zero or more arguments. b5 is able to use many rule files and data files. The order of data files affects only the order of the results, and the order of rule files is insignificant except for that it affects the order of data files included from the rule files. A macro definition has the general form mac => ;;; This will define a macro with the name , taking the arguments and producing, when applied, the string to the result stream (with the arguments replaced by their values). Both the input and macro expansions may contain literals, which have the form <<>> This is equivalent to the string "some text", except that the macros it might include are never expanded. A macro is called, from the data source as well as from the expansions of other macros, by writing its name followed by parentified arguments. The macro and its arguments must be immediately adjacent. Moreover, literals (even empty literals) will be sufficient to separate a macro from its arguments, so that it will not be expanded. So the call could look like, for example, this: amacro(arg1)(arg2)(arg3) 2 Input processing The macro processor reads the data source stream (which is read from at least stdin) and produces a result stream from that (which is eventually printed into stdout). If an expansible macro (macro that has enough arguments) is spotted on the source stream, the following events take place: 1) the expansion of the macro is fetched. 2) the occurrences of arguments in the expansion are replaced by the values given to the arguments. 3) the resulting string is then prepended to the source stream, so macros possibly still existing in the expansion are further expanded. Actually, b5 uses internally a parsed, shared, result-caching representation for the strings. The general idea is the same, however. 3 Command line The program treats its command line arguments as rule files, and the standard input as its source stream. More source streams can be added from a rule file by the statement "input ;;;". 4 Behaviour Let us have the following macros defined: mac 0 f a => a;;; mac 1 f a => f(a);;; mac 2 f a => f(f(a));;; mac succ n f a => f(n(f)(a));;; mac add x y => x(succ)(y);;; mac cat a b => a<<<>>>b;;; mac double t => cat(t)(t);;; mac call_with_itself f => f(f);;; Then, the following example inputs will get evaluated thus: Concatenating two strings: cat(foo)(bar) => foo<<<>>>bar => foobar Concatenating a string with itself: double(poks) => cat(poks)(poks) => poks<<<>>>poks => pokspoks Doubling "pum" zero times: 0(double)(pum) => pum Doubling "pum" once: 1(double)(pum) => double(pum) => pum<<<>>>pum => pumpum Doubling "pum" twice: 2(double)(pum) => double(double(pum)) => double(pum)<<<>>>double(pum) => pum<<<>>>pum<<<>>>pum<<<>>>pum => pumpumpumpum Doubling "pum" once more than zero times: succ(0)(double)(pum) => double(0(double)(pum)) => 0(double)(pum)<<<>>>0(double)(pum) => pum<<<>>>pum => pumpum Doubling "pum" once more than once: succ(1)(double)(pum) => double(1(double)(pum)) => 1(double)(pum)<<<>>>1(double)(pum) => double(pum)<<<>>>double(pum) => pum<<<>>>pum<<<>>>pum<<<>>>pum => pumpumpumpum Concatenating "poks " to an empty string once more than twice: succ(2)(cat(poks ))() => cat(poks )(2(cat(poks ))()) => poks <<<>>>2(cat(poks ))() => poks <<<>>>cat(poks )(cat(poks )()) => poks <<<>>>poks <<<>>>cat(poks )() => poks <<<>>>poks <<<>>>poks <<<>>> => poks poks poks Concatenating "a " to an empty string 1+1 times: add(1)(1)(cat(a ))() => 1(succ)(1)(cat(a ))() => succ(1)(cat(a ))() => cat(a )(1(cat(a ))()) => a <<<>>>1(cat(a ))() => a <<<>>>cat(a )() => a <<<>>>a <<<>>> => a a Applying "double" to itself: call_with_itself(double) => double(double) => double<<<>>>double => doubledouble Forming an endless loop: call_with_itself(call_with_itself) => call_with_itself(call_with_itself) => call_with_itself(call_with_itself) => ... As one can see, the empty literal can be used to separate two strings that should get separately evaluated but textually adjacent. Macros are not expanded when they don't get all their arguments (e.g. above "double" was left as is when not followed by a parenthesised expression). From the point of view of macro calls, textual parts coming from different places are rarely combined to form a macro call. The exception is that the macro / closure and the argument may come from different places. For example, if we define mac id text => text;;;, "id(dou)ble(pum)" results in "double(pum)", not "pumpum". But both "id(double)(pum)" and "double(id(pum))" result in "pumpum". Literals always deny expansions, so e.g. both "double<<<>>>(pum)" and "<<>>(pum)" result in "double(pum)".