Source and other implementations.

# The Brzozowski Variations.
#
# A worked example of ECL programming, serving to demonstrate some of the
# language`s features.
#
# Brzozowski derivatives are an elegant (if inefficient) way to implement
# simple regular expressions. The algorithm introduces the elements of the
# *language* of regular expressions (implemented here in the module `regex`),
# and a procedure of repeated *derivation* with respect to some string, in
# order to evaluate whether the regular expression matches the string.
#
# For more examples, see the rest of this repo.

### module regex
# Modules provide namespacing for bindings. The public bindings on the module
# can then be invoked with `module-name.name`.
(: "Constructors for each expression in the language.")
(
 `test-support (expect) importp

 ### set empty
 # `set` binds names to values, which are put on the stack when the name is
 # evaluated.
 # The literal `{k v}` constructs a dictionary with the key ``k` associated with
 # the value ``v`. Dictionary literals don't evaluate their contents, so `type`
 # and `empty` are both literal symbols.
 # When not included inside of a quoting structure like a dictionary literal, a
 # symbol is evaluated when it's pushed to the stack. To push a symbol without
 # evaluating it, we quote it with `` ` ``.
 # Thus, the word `set` consumes a value and a symbol from the stack, and binds
 # the symbol to the value.
 {type empty} `empty set

 ### set epsilon
 {type epsilon} `epsilon set

 ### defp type
 # `def` and `defp` also binds names to values -- but a name bound with `def`
 # unquotes the list to which it was bound when it's evaluated, pushing its
 # elements onto the stack. Thus, binding a program with `def` is equivalent to
 # defining a function.
 # `defp` (which is only meaningful inside of a module) is just like def, but
 # the resulting binding is private; it can't be accessed from outside the
 # module.
 # Lists of words, like the one this line binds to `type`, are called
 # "quotations".
 (`type swap dict.associate) `type defp

 ### defp assoc
 (rolldown put) `assoc defp

 ### def char
 (`char type `value assoc) `char def

 ### def star
 (`star type `value assoc) `star def

 ### def union
 (`union type `right assoc `left assoc) `union def

 ### def concat
 (`concat type `right assoc `left assoc) `concat def

 ### test char
 # Tests are first-class, and tied to modules. This allows us to
 # write tests against module logic even if we don't want to expose that logic
 # publicly.
 # The ECL cli can start a test session with `ecl test`.
 # We have seen quotations consumed by `def`; quotations are also
 # extremely commonly used where another language would reach for a lambda, or
 # anonymous function. Here `expect` consumes a list of quotations, each of
 # which will be evaluated against `\c char`, and will raise an exception if
 # any of them evaluate to 0 (false).
 # The syntax `\c` constructs a char value, one of the primitive datatypes in
 # ECL.
 (\c char
  # Both `[]` and `()` construct lists, including quotations.
  [(`type at `char match?)
   (`value at \c match?)] expect) `char test

 ### test concat
 (\x char \y char concat
  [(`type at `concat match?)
   ([`left `value] at-path \x match?)
   ([`right `value] at-path \y match?)] expect) `concat test

 # `@defm` takes a quoted program and uses it to construct a module, evaluating
 # each binding in the context of the namespace which is passed as a symbol.
 # The `@` convention indicates that this word evaluates in a new unit---that
 # is, a separate "stack transaction" inside the interpreter, isolated from the
 # evaluation context of its surroundings. If an error occurs, the stack is
 # restored to its state before the unit began executing.
) `regex @defm

### module brzo
# ECL strings are naturally multi-line, and don't need escaping or
# triple-quotes. Docstrings are automatically reflowed when displayed via the
# reflection words.
(: "An implementation of regular expressions via Brzozowski derivatives. Construct a regex with the
    constructors from `regex`; then pass them and a test string to `matches?` to evaluate.")
(# `m q import` imports the names in `q` from the module `m`; `importp` does
 # the same but keeps them from leaking out of the module.
 `regex (`char `empty `epsilon `union `concat `star) importp
 `test-support (expect) importp

 ### defp left
 (`left at) `left defp

 ### defp right
 (`right at) `right defp

 ### defp val
 (`value at) `val defp

 ### def nullable?
 # `def` takes an optional value below its quotation: an annotation. This is an
 # ordinary list containing one or both of the reserved symbols `--` and `:`.
 # On the left side of `:` is the *stack effect*. This is documentation of the
 # values that it expects on the stack and the values that it leaves on the
 # stack.
 # It's also a contract for the program when it's called externally (that is,
 # outside the module); in this example, if `nullable?` leaves
 # fewer than or more than one value on the stack, the interpreter will fail
 # with a contract error.
 # On the right side of `:` is the docstring, which documents the meaning of
 # the word.
 # Above we also saw docstring-only annotations associated with modules, where
 # they perform the same function.
 (regex -- bool : "Does the regular expression contain epsilon?")
 (dup `type at
  (`empty (pop 0)
   `epsilon (pop 1)
   `char (pop 0)
   # `bi` is a combinator: a word which consumes one or more quotations and
   # then applies them.
   # The combinator `bi` consumes two quotations from the stack, applies them
   # both to the next value on the stack, and then puts the results back.
   # `case`, which this is nested inside of, also consumes a quotation and
   # evaluates it for control flow.
   `union ((left nullable?) (right nullable?) bi or)
   `concat ((left nullable?) (right nullable?) bi and)
   `star (pop 1)
   # `case` expects an obligatory 1-element "else" clause at the end of its
   # quotation.
   (pop 0)) case) `nullable? def

 ### test 'union is nullable'
 # We can use single-quotes to construct symbols with spaces in them.
 (epsilon \x char union [(nullable?)] expect) `'union is nullable' test

 ### test 'concat needs both sides nullable'
 (epsilon \x char concat [(nullable? not)] expect) `'concat needs both sides nullable' test

 ### defp dispatch
 # `intern` creates a symbol from a string, and `execute` evaluates a symbol
 # already on the operand stack by looking up its binding. Together they allow
 # us to do general-purpose metaprogramming without macros or other
 # "compile-time" logic.
 # In this case, we implement polymorphic `derive` by looking up the type of a
 # value, constructing an identifier from it, and calling that identifier.
 (regex -- s)
 (`type at chars "derive-" swap cat intern) `dispatch defp

 ### def derive
 (regex c -- regex)
 (over dispatch execute) `derive def

 ### def matches?
 (regex string -- bool)
 (swap (derive) fold nullable?) `matches? def

 ### defp derive-char
 (regex c -- regex)
 ((val) dip match? (epsilon) (empty) if) `derive-char defp

 ### defp derive-union
 (regex c -- regex)
 (((left) dip derive) ((right) dip derive) bi union) `derive-union defp

 ### defp derive-concat-base
 (concated c -- base)
 (((left) dip derive) (pop right) bi concat) `derive-concat-base defp

 ### test derive-concat-base
 (\x char \y char concat \x derive-concat-base
  [([left type] at-path `epsilon match?)
   ([right value] at-path \y match?)] expect) `derive-concat-base test

 ### defp left-nullable?
 (regex c -- bool)
 (pop left nullable?) `left-nullable? defp

 ### test left-nullable?
 (epsilon \y char concat \x [(left-nullable?)] expect) `left-nullable? test

 ### defp derive-concat
 (regex c -- regex)
 ((derive-concat-base) (left-nullable?) ((right) dip pair)
  tri
  # Many cases of combinator application are "pure" in the sense that, like the
  # applications of `bi` and `tri` above, the quotation to be applied is known
  # at read-time and constructed statically.
  # However, it's extremely common to need to compose or construct quotations
  # using both static words and values on the stack. This is analogous to
  # partial function application or currying.
  # Here we do that by collecting `(right) dip` into a `pair`, and then
  # composing it with the `derive` program with `cat`.
  # The resulting composite quotation is passed to `when`, which evaluates it
  # conditionally.
  (derive union) cat when
 ) `derive-concat defp

 ### defp derive-concat-with-locals
 # This program has been written in a very purist "pointfree" or "tacit" style,
 # which eschews the abstraction of values into named variables. ECL's
 # concatenative, combinatoric structure lends itself very well to this style.
 # At the same time, ECL does support so-called *local bindings*. names at the
 # beginning of a quotation, between the || delimeters, stand for values that
 # are consumed from the stack and then can be invoked wherever they need to be
 # used.
 # This function is equivalent to `derive-concat`, but uses local variables
 # instead of combinators. We bind the top two values on the stack to `r` (for
 # regex) and `c` (for character), and then refer to them liberally throughout
 # the body of the quotation.
 (|r c|
  r left c derive r right concat
  r left nullable?
  # It's important to note that locals don't behave like variables in languages
  # with lexical scope. `r` and `c` are "in scope" in the body of this
  # quotation, but that "scope" is not inherited by the `(derive union)`
  # quotation; referring to them there would be an error. We need to compose
  # the quotation in the same way as we do above, with `cat`.
  r right c pair (derive union) cat when) `derive-concat-with-locals defp

 ### defp derive-star
 (regex c -- regex)
 (over ((val) dip derive) dip concat) `derive-star defp

 # Quotations are just regular values on the stack, so it's straightforward to
 # bind the same program to two different names.
 `derive-epsilon `derive-empty ((pop pop empty) swap defp) both

 ### test 'matches "(x|zz)*"'
 (\x char
  \z char \z char concat
  union star
  "xxzzxzzxzzzzxx" matches? {kind test-failure} assert
 ) `'matches "(x|zz)*"' test
) `brzo @defm

### module test-support
(
 ### defp build-infra
 # Partially apply infra with a list of values (which will require swapping
 # before running the program)
 (list -- (q -- bool))
 ((swap infra) cons) `build-infra defp

 ### def expect
 # Collect the stack under the quotations, and add it to a program which takes
 # a quotation and runs it on top of that stack.
 # Then apply that program to each quotation, resulting in a list of the
 # results of each assertion program.
 (... qs --)
 ((stack) dip swap build-infra each
  # Look for any failed assertions.
  raze (min) fold1 {kind test-failure} assert) `expect def
) `test-support @defm