Vaguery / klapaucius

A clean, tested, maintainable Push interpreter written in Clojure.
MIT License
31 stars 2 forks source link

Standardize and centralize instruction behavior (again) #173

Closed Vaguery closed 6 years ago

Vaguery commented 6 years ago

Originally, following Push3 and Clojush's model, instructions put their "results" onto the expected stacks directly.

However, this leads to several problems, especially in situations where the interpreter (temporarily) doesn't recognize the type of a result, or where the result is a :ref. Further, several core instructions useful for recursion and iteration produce "continuations" that are pushed onto :exec to be processed in subsequent steps.

The architectural problem entailed by the original approach is the pressure to "check the type" of things pushed directly onto stacks by instructions. It is more satisfying—and maintainable—if all instructions use the same "router" to send their instructions to to the correct place, rather than independently shoving things around wherever the author decides they should go.

Recently, I decided to put "all" results of instructions back onto the :exec stack, and let the router put them where they should be. This also has problems, though, since several instructions intentionally subvert ""exec routing", like :code-quote. Worse, the combinator instructions can be thought of as "working on" an entire stack of stuff; for example, :scalar-yank arguably "consumes" one :scalar as its first argument, and then the entire :scalar stack, and produces as its result a new :scalar stack.

A further complication and point of confusion comes from the role of :binding stacks. These are not "types", but then again neither are core Push items "typed". So the metaphor should be simpler to understand if we simply consider "stack" to mean "specific address", not something associated with a particular type as such.

So it's time to put things back something like they used to be, but more mindfully.

  1. The Router functionality of the Interpreter record will be upgraded to be a self-contained functional object in its own right, not just a static vector. It will include information about all "addressable stacks", not just the explicit stacks but also the :binding stacks, including the :ARGS and other "special" bindings; the router can be queried at runtime to determine the "address" to which an item would be sent, or all "addresses" to which it might be sent. This latter surfaces the "type system" and permits an item to be (1) a vector of numbers and (2) a vector at the same time.
  2. "type" will be abstracted away to mean "address", and expanded to include :binding addresses in addition to traditional :type and :module stacks. This means all "addresses" are in fact reference able as ":refitems, and so raw code can contain:codeor:scalarsaddresses as such. The behavior of some:ref` instructions will have to be adjusted to suit, to avoid (literal) type problems.
  3. Instructions can obtain arguments from addresses only by using the DSL consume-top-of addressname or copy-top-of addressname; items can be pushed to an address by calling push-onto addressname (which will send the item immediately without checking routing), or route-item, which will (immediately!) invoke the router to send the item to the current route destination.
  4. Instructions can read whole stacks only by using consume-entire addressname or copy-entire addressname; an entire address can be replaced only as a whole using replace-entire addressname

(not quite done here)

Problems to address: Introspection can produce "type addresses" in code, or "type signatures" of code blocks. Care will need to be taken to avoid accidentally permitting inappropriate items to end up on "typed" stacks.

Vaguery commented 6 years ago

Possibly affected by #175

Vaguery commented 6 years ago

Replaced by Possibly affected by #175