mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-23 23:12:04 +03:00
2572 lines
106 KiB
Markdown
2572 lines
106 KiB
Markdown
Reference
|
|
=========
|
|
|
|
Data Models
|
|
-----------
|
|
|
|
### `++axle`, formal state
|
|
|
|
++ axle :: all %ford state
|
|
$: %1 :: version for update
|
|
pol=(map ship baby) ::
|
|
== ::
|
|
|
|
This is the formal state of our vane. Anything that must be remembered
|
|
between calls to ford must be stored here. The number `%1` is a version
|
|
number for our state that allows us to upgrade the structure of our
|
|
state in the future if we wish.
|
|
|
|
`pol` is the a map from every ship on our pier to their individual ford
|
|
state. There is no shared ford state -- every ship is entirely separate.
|
|
|
|
### `++baby`, state by ship
|
|
|
|
++ baby :: state by ship
|
|
$: tad=[p=@ud q=(map ,@ud task)] :: tasks by number
|
|
dym=(map duct ,@ud) :: duct to task number
|
|
jav=(map ,* calx) :: cache
|
|
== ::
|
|
|
|
This is the state specific to each ship.
|
|
|
|
`tad` and `dym` keep track of the tasks we're currently working on.
|
|
`dym` is a map from ducts to task numbers, and `q.tad` is a map from
|
|
task number to the task itself. `p.tad` is the next available task
|
|
number. Thus, the keys of `q.tad` are a subset of the numbers less than
|
|
`p.tad`, and ford has attempted exactly `p.tad` tasks so far.
|
|
|
|
`jav` is the cache of previously-solved problems. The keys are a pair of
|
|
a term (either `%hood`, `%slap`, or `%slam`) and a noun that represents
|
|
the exact problem solved. In the case of a `%hood`, then, the key is of
|
|
the form `[%hood beam cage]`. For `%slap`, there is `[%slap vase twig]`.
|
|
For `%slam`, there is `[%slam vase vase]`. The values are the result of
|
|
solving the problem. Note that this cache is wiped in `++stay` when ford
|
|
is reloaded.
|
|
|
|
### `++task`, problem in progress
|
|
|
|
++ task :: problem in progress
|
|
$: nah=duct :: cause
|
|
kas=silk :: problem
|
|
kig=[p=@ud q=(map ,@ud beam)] :: blocks
|
|
== ::
|
|
|
|
This is all the state we keep regarding a particular task. `nah` is the
|
|
duct which asked us to solve the problem, and `kas` is the most recent
|
|
statement of the problem.
|
|
|
|
`kig` keeps track of which resources we are blocked on. Our blocks are
|
|
stored by index in `q.kig`, and the next available index is `p.kig`.
|
|
|
|
### `++silk`, problem
|
|
|
|
++ silk :: construction layer
|
|
$& [p=silk q=silk] :: cons
|
|
$% [%bake p=mark q=beam r=path] :: local synthesis
|
|
[%boil p=mark q=beam r=path] :: general synthesis
|
|
[%call p=silk q=silk] :: slam
|
|
[%cast p=mark q=silk] :: translate
|
|
[%diff p=silk q=silk] :: diff
|
|
[%done p=(set beam) q=cage] :: literal
|
|
[%dude p=tank q=silk] :: error wrap
|
|
[%dune p=(set beam) q=(unit cage)] :: unit literal
|
|
[%mute p=silk q=(list (pair wing silk))] :: mutant
|
|
[%pact p=silk q=silk] :: patch
|
|
[%plan p=beam q=spur r=hood] :: structured assembly
|
|
[%reef ~] :: kernel reef
|
|
[%ride p=twig q=silk] :: silk thru twig
|
|
[%vale p=mark q=ship r=*] :: validate [our his]
|
|
== ::
|
|
|
|
This is the every type of problem we can solve. Every `%exec` kiss that
|
|
requests us to solve a problem must choose one of these problems to
|
|
solve.
|
|
|
|
Because this is both an internal structure used in ford and the public
|
|
interface to ford, we choose to document this structure in our
|
|
discussion of the public interface to ford below.
|
|
|
|
### `++calx`, cache line
|
|
|
|
++ calx :: concrete cache line
|
|
$% [%hood p=calm q=(pair beam cage) r=hood] :: compile
|
|
[%slap p=calm q=[p=vase q=twig] r=vase] :: compute
|
|
[%slam p=calm q=[p=vase q=vase] r=vase] :: compute
|
|
== ::
|
|
|
|
There are three kinds of cache entries. Every entry includes some
|
|
metadata in `p` and is the combination of an input and its output.
|
|
|
|
The input to a `%hood` is the location of the resource and a cage
|
|
representing the data at that location. The output is the hood found by
|
|
compiling the given cage at the given location.
|
|
|
|
The input to a `%slap` is the vase of the subject and the twig of the
|
|
formula against which we are slapping the subject. The output is the
|
|
vase produced by slapping them.
|
|
|
|
The input to a `%slam` is the vase of the subject and the vase of the
|
|
gate which we are slapping. The output is the vase produced by slamming
|
|
them.
|
|
|
|
### `++calm`, cache line metadata
|
|
|
|
++ calm :: cache metadata
|
|
$: laz=@da :: last accessed
|
|
dep=(set beam) :: dependencies
|
|
== ::
|
|
|
|
Every line in the cache needs to have two pieces of metadata. We must
|
|
know the last time this line in the cache was accessed, and we must know
|
|
what are the dependencies of this line.
|
|
|
|
### `++hood`, assembly components
|
|
|
|
++ hood :: assembly plan
|
|
$: zus=@ud :: zuse kelvin
|
|
sur=(list hoot) :: structures
|
|
lib=(list hoof) :: libraries
|
|
fan=(list horn) :: resources
|
|
src=(list hoop) :: program
|
|
== ::
|
|
|
|
When assembling a hook file, we split it into several sections.
|
|
|
|
`zus` is the kelvin version of the required zuse. In general, we assume
|
|
that any newer (lower-numbered) zuse will retain backward compatibility,
|
|
so any newer zuse is also permissible. This number is set with a `/?` at
|
|
the beginning of the file.
|
|
|
|
`sur` is the set of structures included. These are included with the
|
|
`/-` rune. When a structure is included, we look in `/=main=/sur` for
|
|
the given structure and we load the gate there. When compiling, all the
|
|
included structures are collected into a single core placed in the
|
|
subject of the body with a `=>`.
|
|
|
|
`lib` is the set of librarires included. These are included with the
|
|
`/+` rune. When a library is included, we look in `/=main=/lib` for the
|
|
given library and we load the library there. As with structures, all the
|
|
included libraries are collected into a single core placed in the
|
|
subject of the body with a `=>`.
|
|
|
|
`fan` is the set of resources included. These are loaded in many
|
|
different ways and may load resources from any location. These are
|
|
placed in the subject of the body with a `=~`.
|
|
|
|
`src` is the set of twigs or references to twigs in the body of the
|
|
program. Generally, each of these will represent a core, but this is not
|
|
required. When compiling, these are strung together in a `=~`.
|
|
|
|
### `++hoot`
|
|
|
|
++ hoot (pair bean hoof) :: structure gate/core
|
|
|
|
A structures may be either a direct gate or a core. These are
|
|
syntactically distinguished by adding a `*` to the beginning of the
|
|
structure name for a core. The structure itself is a `hoof`.
|
|
|
|
### `++hoof`
|
|
|
|
++ hoof (pair term (unit (pair case ship))) :: resource reference
|
|
|
|
A hoof, which is either a structure or a library, has a name, and it may
|
|
also specify which version of the resource to use and which ship to
|
|
retrieve it from.
|
|
|
|
### `++horn`
|
|
|
|
++ horn :: resource tree
|
|
$% [%ape p=twig] :: /~ twig by hand
|
|
[%arg p=twig] :: /$ argument
|
|
[%day p=horn] :: /| list by @dr
|
|
[%dub p=term q=horn] :: /= apply face
|
|
[%fan p=(list horn)] :: /. list
|
|
[%for p=path q=horn] :: /, descend
|
|
[%hub p=horn] :: /@ list by @ud
|
|
[%man p=(map span horn)] :: /* hetero map
|
|
[%nap p=horn] :: /% homo map
|
|
[%now p=horn] :: /& list by @da
|
|
[%saw p=twig q=horn] :: /; operate on
|
|
[%see p=beam q=horn] :: /: relative to
|
|
[%sic p=tile q=horn] :: /^ cast
|
|
[%toy p=mark] :: /mark/ static
|
|
== ::
|
|
|
|
This is how we represent the static resources hook files can load. The
|
|
discussion of their use from a user's perspective is documented
|
|
elsewhere (link), so we will here only give a description of the data
|
|
structure itself.
|
|
|
|
A `%ape` horn is simply a twig that gets evaluated and placed in the
|
|
subject.
|
|
|
|
A `%arg` is a gate that gets evaluated with a sample of our location and
|
|
our heel.
|
|
|
|
A `%day` is a horn that applies to each of a list of `@dr`-named files
|
|
in the directory.
|
|
|
|
A `%dub` is a term and horn, where the result of a horn is given the
|
|
face of the term.
|
|
|
|
A `%fan` is a list of horns, all at the current directory level.
|
|
|
|
A `%for` is a path and a horn, where the horn is evaluated relative to
|
|
the given path, where the given path is relative to the current
|
|
location.
|
|
|
|
A `%hub` is a horn that applies to each of a list of `@ud`-named files
|
|
in the directory.
|
|
|
|
A `%man` is a map of spans to horns where the result is a set of each
|
|
horn applied to the current directory given the associated face.
|
|
|
|
A `%nap` is a homogenous map where each entry in a directory is handled
|
|
with the same horn and is given a face according to its name.
|
|
|
|
A `%now` is a horn that applies to each of a list of `@da`-named files
|
|
in the directory.
|
|
|
|
A `%saw` is a twig and a horn, where the twig operates on the result of
|
|
the horn.
|
|
|
|
A `%see` is a beam and a horn, where the horn is evaluated at a location
|
|
of the given beam.
|
|
|
|
A `%sic` is a tile and a horn, where the horn is evaluated and cast to
|
|
the type associated with the tile.
|
|
|
|
A `%toy` is simply a mark to be baked.
|
|
|
|
### `++hoop`, body
|
|
|
|
++ hoop :: source in hood
|
|
$% [%& p=twig] :: direct twig
|
|
[%| p=beam] :: resource location
|
|
== ::
|
|
|
|
This is an entry in the body of the hook file. The hoop can either be
|
|
defined directly in the given file or it can be a reference to another
|
|
file. The second is specified with a `//` rune.
|
|
|
|
### `++bolt`, monadic edge
|
|
|
|
++ bolt :: gonadic edge
|
|
|* a=$+(* *) :: product clam
|
|
$: p=cafe :: cache
|
|
$= q ::
|
|
$% [%0 p=(set beam) q=a] :: depends/product
|
|
[%1 p=(set ,[p=beam q=(list tank)])] :: blocks
|
|
[%2 p=(list tank)] :: error
|
|
== ::
|
|
== ::
|
|
|
|
Throughout our computation, we let our result flow through with the set
|
|
of dependencies of the value. At various times, we may wish to either
|
|
throw an error or declare that the actual result cannot be found until a
|
|
particular resource is retrieved. This is a perfect case for a monad, so
|
|
here we define a data structure for it.
|
|
|
|
At every step, we have a cache, so we store that in `p`. In `q` we store
|
|
the data.
|
|
|
|
In the case of `%0`, we have the result in `q` and the set of
|
|
dependencies in `p`.
|
|
|
|
In the case of `%1`, we have a set of dependencies on which we are
|
|
blocking. When this happens, we make a call to clay to get the
|
|
dependencies, and we proceed with the computation when we receive them.
|
|
Technically, we restart the computation, but since every expensive step
|
|
is cached, there is no significant performance penalty to doing this.
|
|
Referential transparency has its uses.
|
|
|
|
In the case of `%2`, we have a hit an error. This gets passed all the
|
|
way through to the calling duct. The list of tanks is some description
|
|
of what went wrong, often including a stack trace.
|
|
|
|
### `++burg`, monadic rule
|
|
|
|
++ burg :: gonadic rule
|
|
|* [a=$+(* *) b=$+(* *)] :: from and to
|
|
$+([c=cafe d=a] (bolt b)) ::
|
|
:: ::
|
|
|
|
To operate on bolts, we use `++cope` as our bind operator, and the
|
|
functions it works on are of type `burg`. Our functions that operate on
|
|
bolts should have a sample of the cache and a value. Their output should
|
|
be a bolt of the output value. Then, `++cope` will only call the
|
|
function when necessary (in the `%0` case), and it will do so without
|
|
the wrapping of a bolt.
|
|
|
|
If you understand monads, this is probably fairly obvious. Otherwise,
|
|
see the discussion on `++cope` (link).
|
|
|
|
Public Interface
|
|
----------------
|
|
|
|
Ford does not export a scry interface, so the only way to interact with
|
|
ford is by sending kisses and receiving gifts. In fact, ford only sends
|
|
accepts one kiss and gives one gift. This is, of course, misleading
|
|
because ford actually does many different things. It does, however, only
|
|
produce one type of thing -- a result of a computation, which is either
|
|
an error or the value produced along with the set of dependencies
|
|
referenced by it.
|
|
|
|
++ kiss :: in request ->$
|
|
$% [%exec p=@p q=(unit silk)] :: make / kill
|
|
== ::
|
|
|
|
The `%exec` gift requests ford to perform a computation on behalf of a
|
|
particular ship. `p` is the ship, and `q` is the computation. If `q` is
|
|
null, then we are requesting that ford cancel the computation that it is
|
|
currently being run along this duct. Thus, if you wish to cancel a
|
|
computation, you must send the kiss along the same duct as the original
|
|
request.
|
|
|
|
Otherwise, we ask ford to perform a certain computation, as defined in
|
|
`++silk`. Since all computations produce the same type of result, we
|
|
will discuss that result before we jump into `++silk`.
|
|
|
|
++ gift :: out result <-$
|
|
$% [%made p=(each bead (list tank))] :: computed result
|
|
== ::
|
|
|
|
We give either a `bead`, which is a result, or a list of tanks, which is
|
|
an error messge, often including a stack trace.
|
|
|
|
++ bead ,[p=(set beam) q=cage] :: computed result
|
|
|
|
This is a set of dependencies required to compute this value and a cage
|
|
of the result with its associated mark.
|
|
|
|
There are twelve possible computations defined in `++silk`.
|
|
|
|
++ silk :: construction layer
|
|
$& [p=silk q=silk] :: cons
|
|
$% [%bake p=mark q=beam r=path] :: local synthesis
|
|
[%boil p=mark q=beam r=path] :: general synthesis
|
|
[%call p=silk q=silk] :: slam
|
|
[%cast p=mark q=silk] :: translate
|
|
[%done p=(set beam) q=cage] :: literal
|
|
[%dude p=tank q=silk] :: error wrap
|
|
[%dune p=(set beam) q=(unit cage)] :: unit literal
|
|
[%mute p=silk q=(list (pair wing silk))] :: mutant
|
|
[%plan p=beam q=spur r=hood] :: structured assembly
|
|
[%reef ~] :: kernel reef
|
|
[%ride p=twig q=silk] :: silk thru twig
|
|
[%vale p=mark q=ship r=*] :: validate [our his]
|
|
== ::
|
|
|
|
First, we allow silks to autocons. A cell of silks is also a silk, and
|
|
the product vase is a cell of the two silks. This obviously extends to
|
|
an arbitrary number of silks.
|
|
|
|
`%bake` tries to functionally produce the file at a given beam with the
|
|
given mark and heel. It fails if there is no way to translate at this
|
|
level.
|
|
|
|
`%boil` functionally produces the file at a given beam with the given
|
|
mark and heel. If there is no way to translate at this beam, we pop
|
|
levels off the stack and attempt to bake there until we find a level we
|
|
can bake. This should almost always be called instead of `%bake`.
|
|
|
|
`%call` slams the result of one silk against the result of another.
|
|
|
|
`%cast` translates the given silk to the given mark, if possible. This
|
|
is one of the critical and fundamental operations of ford.
|
|
|
|
`%done` produces exactly its input. This is rarely used on its own, but
|
|
many silks are recursively defined in terms of other silks, so we often
|
|
need a silk that simply produces its input. A monadic return, if you
|
|
will.
|
|
|
|
`%diff` diffs the two given silks (which must be of the same mark),
|
|
producing a cage of the mark specified in `++mark` in `++grad` for the
|
|
mark of the two silks.
|
|
|
|
`%dude` computes the given silk with the given tank as part of the stack
|
|
trace if there is an error.
|
|
|
|
`%dune` produces an error if the cage is empty. Otherwise, it produces
|
|
the value in the unit.
|
|
|
|
`%mute` takes a silk and a list of changes to make to the silk. At each
|
|
wing in the list we put the value of the associated silk.
|
|
|
|
`%pact` applies the second silk as a patch to the first silk. The second
|
|
silk must be of the mark specified in `++mark` in `++grad` for the mark
|
|
of the first silk.
|
|
|
|
`%plan` performs a structured assembly directly. This is not generally
|
|
directly useful because several other silks perform supersets of this
|
|
functionality. We don't usually have naked hoods outside ford.
|
|
|
|
`%reef` produces a core containing the entirety of zuse and hoon,
|
|
suitable for running arbitrary code against. The mark is `%noun`.
|
|
|
|
`%ride` slaps a twig against a subject silk. The mark of the result is
|
|
`%noun`.
|
|
|
|
`%vale` validates untyped data from a ship against a given mark. This is
|
|
an extremely useful function.
|
|
|
|
Commentary
|
|
==========
|
|
|
|
Parsing Hook Files
|
|
------------------
|
|
|
|
In the commentary on other vanes, we have traced through the lifecycle
|
|
of various external requests. This is generally a very reasonable order
|
|
to examine vanes since it will eventually cover the entire vane, and we
|
|
are never left wondering why we are doing something.
|
|
|
|
For ford, however, it makes more sense to begin by discussing the
|
|
parsing and assembliing of hook files. Many of the possible requests
|
|
require us to assemble hook files, so we may as well examine this
|
|
immediately.
|
|
|
|
First, we will examine the parsing. We parse a file at a beam to a hood
|
|
in `++fade:zo:za`. The top-level parsing rule is `++fair`, which takes a
|
|
beam and produces a rule to parse an entire hood file.
|
|
|
|
A note on the naming scheme: the parsing the combinators that parse into
|
|
a particular structure are conventionally given the same name as the
|
|
structure. Although this locally clobbers the type names, this pattern
|
|
makes obvious the intent of the parsing combinators.
|
|
|
|
We kick off with `++hood:fair`.
|
|
|
|
++ hood
|
|
%+ ifix [gay gay]
|
|
;~ plug
|
|
;~ pose
|
|
(ifix [;~(plug fas wut gap) gap] dem)
|
|
(easy zuse)
|
|
==
|
|
::
|
|
;~ pose
|
|
(ifix [;~(plug fas hep gap) gap] (most ;~(plug com gaw) hoot))
|
|
(easy ~)
|
|
==
|
|
::
|
|
;~ pose
|
|
(ifix [;~(plug fas lus gap) gap] (most ;~(plug com gaw) hoof))
|
|
(easy ~)
|
|
==
|
|
::
|
|
(star ;~(sfix horn gap))
|
|
(most gap hoop)
|
|
==
|
|
|
|
There are five sections to a hood: system version, structures,
|
|
libraries, resources, and body.
|
|
|
|
First, we parse the requested version number of the system. This is
|
|
specified with a unary `/?` rune. If not present, then we default to the
|
|
current version.
|
|
|
|
Second, we may have zero or more `/-` runes followed by a parsing of a
|
|
`++hoot`, which represents a shared structure.
|
|
|
|
Third, we may have zero or more `/+` runes followed by a parsing of a
|
|
`++hoof`, which represents a shared library.
|
|
|
|
Fourth, we may have zero or more other `/` runes (as described in
|
|
`++horn`), which represent program-specific resources to be loaded.
|
|
|
|
Fifth and finally, we must have one or more body statements (hoops),
|
|
which are either direct twigs or `//` runes.
|
|
|
|
++ hoot
|
|
;~ pose
|
|
(stag %| ;~(pfix tar hoof))
|
|
(stag %& hoof)
|
|
==
|
|
|
|
A structure can either be a direct gate, or it can be a simple core.
|
|
Either one is parsed with `++hoof`, so we distinguish the two cases by
|
|
requireing core references to be prefixed by a `*`.
|
|
|
|
++ hoof
|
|
%+ cook |=(a=^hoof a)
|
|
;~ plug
|
|
sym
|
|
;~ pose
|
|
%+ stag ~
|
|
;~(plug ;~(pfix fas case) ;~(pfix ;~(plug fas sig) fed:ag))
|
|
(easy ~)
|
|
==
|
|
==
|
|
|
|
A hoof must have a name, which is a term. Optionally, we also include a
|
|
case and a ship. This is marked by appending a `/` followed by a case to
|
|
denote the requested version of the resource and a `/` followed by a
|
|
ship name to denote the requested source of the resource. For example,
|
|
`resource/1/~zod` requests the first version of `resource` on `~zod`.
|
|
|
|
++ case
|
|
%- sear
|
|
:_ nuck:so
|
|
|= a=coin
|
|
?. ?=([%$ ?(%da %ud %tas) *] a) ~
|
|
[~ u=(^case a)]
|
|
|
|
Here, we parse a literal with `++nuck:so`, and we accept the input if it
|
|
is either an absolute date, an unsigned decimal, or a label.
|
|
|
|
This leaves only horns and hoops to parse. Hoops are much simple to
|
|
parse, so we'll discuss those first.
|
|
|
|
++ hoop
|
|
;~ pose
|
|
(stag %| ;~(pfix ;~(plug fas fas gap) have))
|
|
(stag %& tall:vez)
|
|
==
|
|
|
|
There are two types of hoops. Direct twigs are parsed with
|
|
`++tall:vast`, which is the just the hoon parser for a tall-form twig.
|
|
|
|
References to external twigs are marked with a `//` rune followed by a
|
|
beam, which is parsed with `++have`.
|
|
|
|
++ hath (sear plex:voz (stag %clsg poor:voz)) :: hood path
|
|
++ have (sear tome ;~(pfix fas hath)) :: hood beam
|
|
|
|
`++have` parses a path with `++hath`, and then it converts the path into
|
|
a beam with `++tome`.
|
|
|
|
`++hath` parses a `/`-separated list with `++poor:vast`, then converts
|
|
it to an actual path with `++plex:vast`.
|
|
|
|
This leaves only horns to parse.
|
|
|
|
++ horn
|
|
=< apex
|
|
=| tol=?
|
|
|%
|
|
++ apex
|
|
%+ knee *^horn |. ~+
|
|
;~ pfix fas
|
|
;~ pose
|
|
(stag %toy ;~(sfix sym fas))
|
|
(stag %ape ;~(pfix sig ape:read))
|
|
(stag %arg ;~(pfix buc ape:read))
|
|
(stag %day ;~(pfix bar day:read))
|
|
(stag %dub ;~(pfix tis dub:read))
|
|
(stag %fan ;~(pfix dot fan:read))
|
|
(stag %for ;~(pfix com for:read))
|
|
(stag %hub ;~(pfix pat day:read))
|
|
(stag %man ;~(pfix tar man:read))
|
|
(stag %nap ;~(pfix cen day:read))
|
|
(stag %now ;~(pfix pam day:read))
|
|
(stag %saw ;~(pfix sem saw:read))
|
|
(stag %see ;~(pfix col see:read))
|
|
(stag %sic ;~(pfix ket sic:read))
|
|
==
|
|
==
|
|
|
|
Horn parsing is slightly complex, so we create an internal core to
|
|
organize our code. Our core has a global variable of `tol`, which is
|
|
true if tall form is permissible and false if we're already in wide
|
|
form. We kick off the parsing with `++apex`.
|
|
|
|
`++apex` specifies how each rune is parsed. This allows us to offload
|
|
the different ways of parsing the arguments to these runes into separate
|
|
arms. The exception here is that the `%toy` horn is simply of the form
|
|
`/mark/`.
|
|
|
|
We'll examine each of the horn parsing arms right after we discuss
|
|
`++rail`, which is used in each one.
|
|
|
|
++ rail
|
|
|* [wid=_rule tal=_rule]
|
|
?. tol wid
|
|
;~(pose wid tal)
|
|
|
|
This takes a wide-form and a tall-form parsing rule. If tall form is
|
|
permissible, then it allows either rule to match; else, it allows only
|
|
the wide form rule.
|
|
|
|
++ read
|
|
|% ++ ape
|
|
%+ rail
|
|
(ifix [sel ser] (stag %cltr (most ace wide:vez)))
|
|
;~(pfix gap tall:vez)
|
|
|
|
`++ape:read` parses for both the `/~` and the `/$` runes. It produces a
|
|
twig. The wide form is a tuple of one or more ace-separated wide-form
|
|
twigs parsed with `++wide:vast` and surrounded by `[` and `]`. The tall
|
|
form is a single tall form twig parsed by `++tall:vast`
|
|
|
|
++ day
|
|
%+ rail
|
|
apex(tol |)
|
|
;~(pfix gap apex)
|
|
|
|
This parses for the `/|`, `/@`, `/%`, and `/&` runes. It produces a
|
|
horn. The wide form is, recursively, the entire horn parser with tall
|
|
form disabled. The tall form is a gap followed by, recursively, the
|
|
entire horn parser.
|
|
|
|
++ dub
|
|
%+ rail
|
|
;~(plug sym ;~(pfix tis apex(tol |)))
|
|
;~(pfix gap ;~(plug sym ;~(pfix gap apex)))
|
|
|
|
This parses for the `/=` rune. It produces a term followed by a horn.
|
|
The wide form is a symbol name followed by a `=` and, recursively, the
|
|
entire horn parser with tall form disabled. The tall form is a gap
|
|
followed by a symbol name, another gap, and, recursively, the entire
|
|
horn parser.
|
|
|
|
++ fan
|
|
%+ rail fail
|
|
;~(sfix (star ;~(pfix gap apex)) ;~(plug gap duz))
|
|
|
|
This parses for the `/.` rune. It produces a list of horns. There is no
|
|
wide form. The tall form is a stet-terminated series of gap-separated
|
|
recursive calls to the entire horn parser.
|
|
|
|
++ for
|
|
%+ rail
|
|
;~(plug (ifix [sel ser] hath) apex(tol |))
|
|
;~(pfix gap ;~(plug hath ;~(pfix gap apex)))
|
|
|
|
This parses for the `/,` rune. It produces a path and a horn. The wide
|
|
form is a `[`-`]`-surrounded path followed by, recursively, the entire
|
|
horn parser with tall form disabled. The tall form is a gap followed by
|
|
a path, another gap, and, recursively, the entire horn parser.
|
|
|
|
++ man
|
|
%+ rail fail
|
|
%- sear
|
|
:_ ;~(sfix (star ;~(pfix gap apex)) ;~(plug gap duz))
|
|
|= fan=(list ^horn)
|
|
=| naf=(list (pair term ^horn))
|
|
|- ^- (unit (map term ^horn))
|
|
?~ fan (some (~(gas by *(map term ^horn)) naf))
|
|
?. ?=(%dub -.i.fan) ~
|
|
$(fan t.fan, naf [[p.i.fan q.i.fan] naf])
|
|
|
|
This parses for the `/*` rune. It produces a map of spans to horns.
|
|
There is no wide form. The tall form is a stet-terminated series of
|
|
gap-separated recursive calls to the entire horn parser. All produced
|
|
horns are expected to be from `/=` runes. The term and horn in each `/=`
|
|
horn is inserted into the produced map as a key-value pair.
|
|
|
|
++ saw
|
|
%+ rail
|
|
;~(plug ;~(sfix wide:vez sem) apex(tol |))
|
|
;~(pfix gap ;~(plug tall:vez ;~(pfix gap apex)))
|
|
|
|
This parses for the `/;` rune. It produces a twig and a horn. The wide
|
|
form is a wide-form twig followed by a `;` and, recursively, the entire
|
|
horn parser with tall form disabled. The tall form is a gap followed by
|
|
a tall-form twig, another gap, and, recursively, the entire horn parser.
|
|
|
|
++ see
|
|
%+ rail
|
|
;~(plug ;~(sfix have col) apex(tol |))
|
|
;~(pfix gap ;~(plug have ;~(pfix gap apex)))
|
|
|
|
This parses for the `/:` rune. It produces a beam and a horn. The wide
|
|
form is a beam followed by a `;` and, recursively, the entire horn
|
|
parser with tall form disabled. The tall form is a gap followed by a
|
|
beam, another gap, and, recursively, the entire horn parser.
|
|
|
|
++ sic
|
|
%+ rail
|
|
;~(plug ;~(sfix toil:vez ket) apex(tol |))
|
|
;~(pfix gap ;~(plug howl:vez ;~(pfix gap apex)))
|
|
--
|
|
|
|
This parses for the `/^` rune. It produces a tile and a horn. The wide
|
|
form is a wide-form tile, parsed with `++toil:vast`, followed by a `^`
|
|
and, recursively, the entire horn parser with tall form disabled. The
|
|
tall form is a gap followed by a tall-form tile, parsed with
|
|
`++howl:vast`, another gap, and, recursively, the entire horn parser.
|
|
|
|
Assembling Hook Files
|
|
---------------------
|
|
|
|
At this point, we've parsed a hook file into a hood. We will now
|
|
describe exactly how this hood is assembled into a vase. The problem of
|
|
assembling is handled entirely within the `++meow:zo:za` core.
|
|
|
|
++ meow :: assemble
|
|
|= [how=beam arg=heel]
|
|
=| $: rop=(map term (pair hoof twig)) :: structure/complex
|
|
zog=(set term) :: structure guard
|
|
bil=(map term (pair hoof twig)) :: libraries known
|
|
lot=(list term) :: library stack
|
|
zeg=(set term) :: library guard
|
|
boy=(list twig) :: body stack
|
|
hol=? :: horns allowed?
|
|
==
|
|
|%
|
|
|
|
We take two arguments and keep seven pieces of state. `how` is the
|
|
location of the hook file we're assembling, and `arg` is the heel, or
|
|
virtual path extension, of the file.
|
|
|
|
In `rop`, we maintain a map of terms to pairs of hooves and twigs to
|
|
represent the structures we've encountered that we will put together in
|
|
a core at the top of the file.
|
|
|
|
In `zog`, we maintain the set of structures we're in the middle of
|
|
loading. If we try to load a structure already in our dependency
|
|
ancestry, then we fail because we do not allow circular dependencies.
|
|
This enforces that our structure dependency graph is a DAG.
|
|
|
|
In `bil`, we maintain a map of terms to pairs of hooves and twigs to
|
|
represent the libraries we've encountered that we will put together in a
|
|
series of cores after the structure core.
|
|
|
|
In `lot`, we maintain a stack of library names as they are encountered
|
|
during a depth-first search. More precisely, we push a library onto the
|
|
stack after we've processed all its children. Thus, every library
|
|
depends only on things deeper in the list. The libraries must be loaded
|
|
in the reverse of this order. Concisely, this is a topological sort of
|
|
the library dependency partial ordering.
|
|
|
|
In `zeg`, we maintain the set of libraries we're in the middle of
|
|
loading. If we try to load a library already in our dependency ancestry,
|
|
then we fail because we do not allow circular dependencies. This
|
|
enforces that our library dependency graph is a DAG.
|
|
|
|
In `boy`, we maintain a stack of body twigs, which we'll put together in
|
|
a series of cores at the end of the file.
|
|
|
|
In `hol`, we decide if we're allowed to contain horns. Libraries and
|
|
structures are not allowed to contain horns.
|
|
|
|
We in every case enter `++meow` through `++abut`. You'll notice that
|
|
there are four (count 'em, four!) calls to `++cope` in `++abut`. If
|
|
you've glanced at the ford code in general, you've probably seen cope
|
|
over and over. It is called in 79 different places. We need to discuss
|
|
the use of this critical function in detail, so we may as well do it
|
|
here.
|
|
|
|
++ cope :: bolt along
|
|
|* [hoc=(bolt) fun=(burg)]
|
|
?- -.q.hoc
|
|
%2 hoc
|
|
%1 hoc
|
|
%0 =+ nuf=(fun p.hoc q.q.hoc)
|
|
:- p=p.nuf
|
|
^= q
|
|
?- -.q.nuf
|
|
%2 q.nuf
|
|
%1 q.nuf
|
|
%0 [%0 p=(grom `_p.q.nuf`p.q.hoc p.q.nuf) q=q.q.nuf]
|
|
== ==
|
|
|
|
In monad-speak, this is the bind operator for the bolt monad. If monads
|
|
aren't your thing, don't worry, we're going to explain the use of cope
|
|
without further reference to them.
|
|
|
|
Recall that there are three different types of bolt. A `%2` error bolt
|
|
contains a list of tanks describing the error, a `%1` block bolt
|
|
contains a set of resources we're blocked on, and a `%0` value bolt
|
|
contains an actual value and the set of its dependencies.
|
|
|
|
We most commonly want to perform an operation on the value in a bolt if
|
|
it is a `%0` bolt. If it's not a `%0` bolt, we want to leave it alone.
|
|
This requires us to write a certain amount of boilerplate between each
|
|
of our operations to see if any of them produced a `%1` or a `%2` bolt.
|
|
This gets tiresome, so we pull it out into a separate arm and call it
|
|
`++cope`.
|
|
|
|
Intuitively, we're calling the function `fun` with the value in `hoc`,
|
|
where `fun` takes an argument of type whatever is the value in a `%0`
|
|
case of `hoc`, and it produces a bolt of some (possibly different) type.
|
|
For brevity, we will refer to the type of the of the value in the `%0`
|
|
case of a bolt as the "type of the bolt".
|
|
|
|
If the `hoc` bolt we're given as input to `fun` is already a `%1` or a
|
|
`%2` bolt, then we simply produce that. We don't even try to run `fun`
|
|
on it.
|
|
|
|
Otherwise, we run `fun` with the arguments from the bolt and, if it
|
|
produces a `%1` or a `%2` bolt, we simply produce that. If it produces a
|
|
`%0` bolt, then we produce that with the old set of dependencies merged
|
|
in with the new set.
|
|
|
|
We'll see more about how the bolt monad works as we run into more
|
|
interesting uses of it. For now, this is sufficient to move on with
|
|
`++abut`.
|
|
|
|
++ abut :: generate
|
|
|= [cof=cafe hyd=hood]
|
|
^- (bolt vase)
|
|
%+ cope (apex cof hyd)
|
|
|= [cof=cafe sel=_..abut]
|
|
=. ..abut sel
|
|
%+ cope (maim cof pit able)
|
|
|= [cof=cafe bax=vase]
|
|
%+ cope (chap cof bax [%fan fan.hyd])
|
|
|= [cof=cafe gox=vase]
|
|
%+ cope (maim cof (slop gox bax) [%tssg (flop boy)])
|
|
|= [cof=cafe fin=vase]
|
|
(fine cof fin)
|
|
|
|
Our job is simple: we must assemble a hood file into a vase. Hopefully,
|
|
the usage of `++cope` is fairly understandable. The correct way to read
|
|
this is that it does essentially five things.
|
|
|
|
First, we call `++apex` to process the structures, libraries, and body.
|
|
This changes our state, so we set our context to the produced context.
|
|
Second, we call `++able` to assemble the strucutres and libraries into a
|
|
twig, which we slap against zuse with `++maim`. Third, we call `++chap`
|
|
to process the resources in the context of the already-loaded structures
|
|
and libraries. Fourth, we slap the body against the structures,
|
|
libraries, and resources. Fifth and finally, we produce the resultant
|
|
vase.
|
|
|
|
++ apex :: build to body
|
|
|= [cof=cafe hyd=hood]
|
|
^- (bolt ,_..apex)
|
|
?. |(hol ?=(~ fan.hyd))
|
|
%+ flaw cof :_ ~ :- %leaf
|
|
"horns not allowed in structures and libraries: {<[how arg]>}"
|
|
%+ cope (body cof src.hyd)
|
|
|= [cof=cafe sel=_..apex]
|
|
=. ..apex sel
|
|
%+ cope (neck cof lib.hyd)
|
|
|= [cof=cafe sel=_..apex]
|
|
=. ..apex sel(boy boy)
|
|
%+ cope (head cof sur.hyd)
|
|
|= [cof=cafe sel=_..apex]
|
|
(fine cof sel)
|
|
|
|
First, we make sure that if we're not allowed to have horns, we don't.
|
|
Otherwise, we produce and error with `++flaw`.
|
|
|
|
++ flaw |=([a=cafe b=(list tank)] [p=a q=[%2 p=b]]) :: bolt from error
|
|
|
|
This produces a `%2` error bolt from a list of tanks. Fairly trivial.
|
|
|
|
We should be starting to get used to the cope syntax, so we can see that
|
|
we really only do three things here. We process the body with `++body`,
|
|
the libraries with `++neck`, and the structures with `++head`.
|
|
|
|
++ body :: produce functions
|
|
|= [cof=cafe src=(list hoop)]
|
|
^- (bolt _..body)
|
|
?~ src (fine cof ..body)
|
|
%+ cope (wilt cof i.src)
|
|
|= [cof=cafe sel=_..body]
|
|
^$(cof cof, src t.src, ..body sel)
|
|
|
|
We must process a list of hoops that represent our body. If there are no
|
|
more hoops, we just produce our context in a `%0` bolt with `++fine`.
|
|
|
|
++ fine |* [a=cafe b=*] :: bolt from data
|
|
[p=`cafe`a q=[%0 p=*(set beam) q=b]] ::
|
|
|
|
In monad-speak, this is the return operator. For us, this just means
|
|
that we're producing a `%0` bolt, which contains a path and a set of
|
|
dependencies. We assume there are no dependencies for the given data, or
|
|
that they will be added later.
|
|
|
|
If there are more hoops in `++body`, we call `++wilt` to process an
|
|
individual hoop and recurse.
|
|
|
|
++ wilt :: process body entry
|
|
|= [cof=cafe hop=hoop]
|
|
^- (bolt _..wilt)
|
|
?- -.hop
|
|
%& (fine cof ..wilt(boy [p.hop boy]))
|
|
%|
|
|
%+ cool |.(leaf/"ford: wilt {<[(tope p.hop)]>}")
|
|
%+ cope (lend cof p.hop)
|
|
|= [cof=cafe arc=arch]
|
|
?: (~(has by r.arc) %hoon)
|
|
%+ cope (fade cof %hoon p.hop)
|
|
|= [cof=cafe hyd=hood]
|
|
%+ cope (apex(boy ~) cof hyd)
|
|
|= [cof=cafe sel=_..wilt]
|
|
(fine cof sel(boy [[%tssg boy.sel] boy]))
|
|
=+ [all=(lark (slat %tas) arc) sel=..wilt]
|
|
%+ cope
|
|
|- ^- (bolt (pair (map term foot) _..wilt))
|
|
?~ all (fine cof ~ ..wilt)
|
|
%+ cope $(all l.all)
|
|
|= [cof=cafe lef=(map term foot) sel=_..wilt]
|
|
%+ cope ^$(all r.all, cof cof, sel sel)
|
|
|= [cof=cafe rig=(map term foot) sel=_..wilt]
|
|
%+ cope
|
|
%= ^^^^$
|
|
cof cof
|
|
..wilt sel(boy ~)
|
|
s.p.hop [p.n.all s.p.hop]
|
|
==
|
|
|= [cof=cafe sel=_..wilt]
|
|
%+ fine cof
|
|
[`(map term foot)`[[p.n.all [%ash [%tssg boy.sel]]] lef rig] sel]
|
|
|= [cof=cafe mav=(map term foot) sel=_..wilt]
|
|
?~ mav
|
|
(flaw cof [%leaf "source missing: {<(tope p.hop)>}"]~)
|
|
(fine cof sel(boy [[%brcn mav] boy]))
|
|
==
|
|
|
|
In the case of a direct twig hoop, we just push it onto `boy` and we're
|
|
done. In the case of an indirect hoop, we must compile the referenced
|
|
file.
|
|
|
|
First, we push onto the stack trace a message indicating which file
|
|
exactly we're compiling at the moment with `++cool`.
|
|
|
|
++ cool :: error caption
|
|
|* [cyt=trap hoc=(bolt)]
|
|
?. ?=(%2 -.q.hoc) hoc
|
|
[p.hoc [%2 *cyt p.q.hoc]]
|
|
|
|
If an error occurred in computing `hoc`, we put the bunt of `cyt` onto
|
|
the stack trace. Thus, `cyt` is not evaluated at all unless an error
|
|
occurred.
|
|
|
|
Next in `++wilt`, we load the information about the filesystem node
|
|
referenced by the hoop with `++lend`.
|
|
|
|
++ lend :: load arch
|
|
|= [cof=cafe bem=beam]
|
|
^- (bolt arch)
|
|
=+ von=(ska %cy (tope bem))
|
|
?~ von [p=cof q=[%1 [bem ~] ~ ~]]
|
|
(fine cof ((hard arch) (need u.von)))
|
|
|
|
This is a simple call to the namespace. If the resource does not yet
|
|
exist, we block on it by producing a `%1` bolt. Otherwise, we cast it to
|
|
an arch and produce this.
|
|
|
|
Continuing in `++wilt`, we examine the produced arch. If the referenced
|
|
filesystem node has a `hoon` child node, then we've found the required
|
|
source, so we parse it with `++fade`. Recall that we referred earlier to
|
|
`++fade`. The salient point there is that it takes a beam, reads in the
|
|
hook file there, and parses it into a hood file with `++fair`.
|
|
|
|
Now, we simply recurse on `++apex` to compile the new hood. Note that,
|
|
while we do clear the `boy` list, we do not clear the other lists. Thus,
|
|
we are accumulating all the structures and libraries referenced in all
|
|
the referenced hook files in one group, which we will put at the top of
|
|
the product.
|
|
|
|
After this, we put the new list of body twigs into a `=~`, push this
|
|
onto our old list of body twigs, and produce the result.
|
|
|
|
If there is no hoon file here, then we descend into each of our children
|
|
until we find a hoon file. First, we produce a list of all our children
|
|
whose names are terms with `++lark`.
|
|
|
|
++ lark :: filter arch names
|
|
|= [wox=$+(span (unit ,@)) arc=arch]
|
|
^- (map ,@ span)
|
|
%- ~(gas by *(map ,@ span))
|
|
=| rac=(list (pair ,@ span))
|
|
|- ^+ rac
|
|
?~ r.arc rac
|
|
=. rac $(r.arc l.r.arc, rac $(r.arc r.r.arc))
|
|
=+ gib=(wox p.n.r.arc)
|
|
?~(gib rac [[u.gib p.n.r.arc] rac])
|
|
|
|
We traverse the children map of `arc` to filter out those children whose
|
|
names aren't accepted by `wox` and produce a map from the product of
|
|
`wox` to the original name. `++lark` is used in many cases to parse
|
|
names into other types, like numbers or dates, ignoring those which do
|
|
not fit the format. In `++wilt`, though, we simply want to filter out
|
|
those children whose names are not terms.
|
|
|
|
Next, we will produce a map from terms to feet. Each of these feet will
|
|
be placed in a core named by the child name, and it will contain arms
|
|
according to its children. Thus, if the indirect hoop references
|
|
`/path`, then to access the twig defined in `/path/to/twig/hoon`, our
|
|
body must refer to `twig:to`.
|
|
|
|
If there are no more children, then we are done, so we produce our
|
|
current context.
|
|
|
|
Else, we recurse into the left and right sides of our map. Finally, we
|
|
process our current entry in the map. We first recurse by calling
|
|
`++wilt` one level down. Thus, in the previous example, the first time
|
|
we get to this point we are processing `/path`, so we recurse on
|
|
`++wilt` with path `/path/to`. We also remove our current body from the
|
|
recursion, so that we may add it back in later the way we want to.
|
|
|
|
After recursing, we push the new body onto our map, keyed by its name.
|
|
We also produce the new context so that all external structures,
|
|
libraries, and resources are collected into the same place.
|
|
|
|
Finally, we have a map of names to feet. If this map is empty, then
|
|
there were no twigs at the requested path, so we give an error with
|
|
`++flaw`.
|
|
|
|
If the map is nonempty, then we finally produce our context with with
|
|
one thing pushed onto the front: a core made out of the map we just
|
|
produced.
|
|
|
|
This concludes our discussion of `++wilt` and `++body`. Thus, it remains
|
|
in `++apex` to discuss `++neck` and `++head`.
|
|
|
|
++ neck :: consume libraries
|
|
|= [cof=cafe bir=(list hoof)]
|
|
^- (bolt ,_..neck)
|
|
?~ bir (fine cof ..neck)
|
|
?: (~(has in zeg) p.i.bir)
|
|
(flaw cof [%leaf "circular library dependency: {<i.bir>}"]~)
|
|
=+ gez=(~(put in zeg) p.i.bir)
|
|
=+ byf=(~(get by bil) p.i.bir)
|
|
?^ byf
|
|
?. =(`hoof`i.bir `hoof`p.u.byf)
|
|
(flaw cof [%leaf "library mismatch: {<~[p.u.byf i.bir]>}"]~)
|
|
$(bir t.bir)
|
|
=+ bem=(hone %core %lib i.bir)
|
|
%+ cope (fade cof %hook bem)
|
|
|= [cof=cafe hyd=hood]
|
|
%+ cope (apex(zeg gez, hol |, boy ~) cof hyd)
|
|
|= [cof=cafe sel=_..neck]
|
|
=. ..neck
|
|
%= sel
|
|
zeg zeg
|
|
hol hol
|
|
lot [p.i.bir lot]
|
|
bil (~(put by bil) p.i.bir [i.bir [%tssg (flop boy.sel)]])
|
|
==
|
|
^^$(cof cof, bir t.bir)
|
|
|
|
Here, we're going to consume the list of libraries and place them in
|
|
`bil`. If there are no more libraries, we're done, so we just produce
|
|
our current context.
|
|
|
|
Otherwise, we check to see if the next library in the list is in `zeg`.
|
|
If so, then this library is one of the libraries that we're already in
|
|
the middle of compiling. There is a circular dependency, so we fail.
|
|
|
|
Otherwise, we let `gez` be `zeg` plus the current library so that while
|
|
compiling the dependencies of this library we don't later create a
|
|
circular dependency. We check next to see if this library is alredy in
|
|
`bil`. If so, then we have already included this library earlier, so we
|
|
check to see if this is the same version of the library as we included
|
|
earlier. If so, we skip it. Else, we fail since we can't include two
|
|
different versions of a library. We really should allow for newer
|
|
versions of a library since in kelvin versioning we assume backwards
|
|
compatibility, but for now we require an exact match.
|
|
|
|
If we haven't already included this library, then we're going to do
|
|
that. First, we get the location of the library with `++hone`.
|
|
|
|
++ hone :: plant hoof
|
|
|= [for=@tas way=@tas huf=hoof]
|
|
^- beam
|
|
?~ q.huf
|
|
how(s ~[for p.huf way])
|
|
[[q.u.q.huf %main p.u.q.huf] ~[for p.huf way]]
|
|
|
|
If we haven't specified the version of the library, we use the current
|
|
ship, desk, and case. Otherwise, we use the given ship and case on desk
|
|
`%main`. In either case, the path is `/way/p.huf/for`. In the case of
|
|
`++neck`, this means `/lib/core/[library name]`.
|
|
|
|
In `++neck`, we next compile the hook file at that location with
|
|
`++fade`. Again, we will delay the discussion of `++fade`, noting only
|
|
that it takes a beam and parses the hook file there into a hood.
|
|
|
|
We recurse on this to compile the library. During the compilation, we
|
|
let `zeg` be `gez` to avoid circular dependencies, we let `hol` be false
|
|
since we don't allow horns in libraries, and we let `boy` be null so
|
|
that we can isolate the new body twigs.
|
|
|
|
Next, we reintegrate the new data into our context. We use the context
|
|
created by the recursion with four changes. First, we reset `zeg` to our
|
|
old `zeg`. Second, we reset `hol` to our old `hol`. Third, we put the
|
|
name of our library onto the stack of libraries. This means all of a
|
|
libraries dependencies will be earlier in `lot` than the library itself,
|
|
making `lot` a topological ordering on the dependency graph. Fourth, we
|
|
put in `bil` the library hoof and body (with all body twigs collected in
|
|
a `=~`), keyed by the library name.
|
|
|
|
Finally, we recurse, processing the next library in our list.
|
|
|
|
To complete our disucssion of `++apex`, we must process our structures.
|
|
|
|
++ head :: consume structures
|
|
|= [cof=cafe bir=(list hoot)]
|
|
|- ^- (bolt ,_..head)
|
|
?~ bir
|
|
(fine cof ..head)
|
|
?: (~(has in zog) p.q.i.bir)
|
|
(flaw cof [%leaf "circular structure dependency: {<i.bir>}"]~)
|
|
=+ goz=(~(put in zog) p.q.i.bir)
|
|
=+ byf=(~(get by rop) p.q.i.bir)
|
|
?^ byf
|
|
?. =(`hoof`q.i.bir `hoof`p.u.byf)
|
|
(flaw cof [%leaf "structure mismatch: {<~[p.u.byf q.i.bir]>}"]~)
|
|
$(bir t.bir)
|
|
=+ bem=(hone ?:(p.i.bir %gate %core) %sur q.i.bir)
|
|
%+ cope (fade cof %hook bem)
|
|
|= [cof=cafe hyd=hood]
|
|
%+ cope (apex(zog goz, hol |, boy ~) cof hyd)
|
|
|= [cof=cafe sel=_..head]
|
|
?. =(bil bil.sel)
|
|
(flaw cof [%leaf "structures cannot include libraries: {<i.bir>}"]~)
|
|
=. ..head
|
|
%= sel
|
|
boy ?: p.i.bir
|
|
boy
|
|
(welp boy [[[%cnzy p.q.i.bir] [%$ 1]] ~])
|
|
zog zog
|
|
hol hol
|
|
rop %+ ~(put by (~(uni by rop) rop.sel))
|
|
p.q.i.bir
|
|
[q.i.bir [%tssg (flop boy.sel)]]
|
|
==
|
|
^^$(cof cof, bir t.bir)
|
|
|
|
The processing of our structures is very similar to that of our
|
|
libraries. For clarity, we'll use many of the same phrases in describing
|
|
the parallel natures. First, we check to see if there are more
|
|
structures to process. If not, we're done, so we produce our context.
|
|
|
|
Otherwise, we let `goz` be `zog` plus the current structure so that
|
|
while compiling the dependencies of this structure we don't later create
|
|
a circular dependency. We check next to see if this structure is alredy
|
|
in `rop`. If so, then we have already included this structure earlier,
|
|
so we check to see if this is the same version of the structure as we
|
|
included earlier. If so, we skip it. Else, we fail since we can't
|
|
include two different versions of a structure.
|
|
|
|
If we haven't loaded this structure, then we call `++hone` to get the
|
|
beam where the file structure should be. If the loobean in the hoot is
|
|
true, then we're looking for a gate; otherwise, we're looking for a
|
|
core. We parse this file with `++fade`.
|
|
|
|
Now, we recurse on this to compile the structure. During the recursion,
|
|
there we have threee changes. Frist, we let `zog` be `goz` so that we
|
|
don't create a circular dependency. Second, we let `hol` be false since
|
|
we do not allow horns in structures. Third, we let `boy` be null so that
|
|
we can isolate the new body twigs.
|
|
|
|
Next, we reintegrate the new data into our context. We use the context
|
|
cretaed by the recursion with four changes. First, if we're including a
|
|
gate structure, then we reset the body to its original body. Else we put
|
|
on the top of our list of body twigs what is essentially a
|
|
`=+ structure-name` to take off the face of the structure. Second, we
|
|
reset `zog` to our old `zog`. Third, we reset `hol` to our old `hol`.
|
|
Finally, we put in `rop` the structure hoof and body (with all body
|
|
twiggs collected in a `=~`), keyed by the structure name.
|
|
|
|
Finally, we recurse, processing the next structure in our list.
|
|
|
|
This concludes our discussion of `++apex`.
|
|
|
|
++ abut :: generate
|
|
|= [cof=cafe hyd=hood]
|
|
^- (bolt vase)
|
|
%+ cope (apex cof hyd)
|
|
|= [cof=cafe sel=_..abut]
|
|
=. ..abut sel
|
|
%+ cope (maim cof pit able)
|
|
|= [cof=cafe bax=vase]
|
|
%+ cope (chap cof bax [%fan fan.hyd])
|
|
|= [cof=cafe gox=vase]
|
|
%+ cope (maim cof (slop gox bax) [%tssg (flop boy)])
|
|
|= [cof=cafe fin=vase]
|
|
(fine cof fin)
|
|
|
|
Returning to `++abut`, we have now processed the structures, libraries
|
|
and body twigs. Next, we slap our preamble (structures and libraries)
|
|
against zuse. First, we construct our preamble in `++able`.
|
|
|
|
++ able :: assemble preamble
|
|
^- twig
|
|
:+ %tsgr
|
|
?:(=(~ rop) [%$ 1] [%brcn (~(run by rop) |=([* a=twig] [%ash a]))])
|
|
[%tssg (turn (flop lot) |=(a=term q:(need (~(get by bil) a))))]
|
|
|
|
We first put the structures in `rop` into a single `|%` at the top and
|
|
`=>` it onto a `=~` of our libraries, in the reverse order that they
|
|
appear in `lot`. Thus, the structures are in a single core while the
|
|
libraries are in consecutive cores.
|
|
|
|
We slap the preamble against zuse with `++maim`.
|
|
|
|
++ maim :: slap
|
|
|= [cof=cafe vax=vase gen=twig]
|
|
^- (bolt vase)
|
|
%+ (clef %slap) (fine cof vax gen)
|
|
|= [cof=cafe vax=vase gen=twig]
|
|
=+ puz=(mule |.((~(mint ut p.vax) [%noun gen])))
|
|
?- -.puz
|
|
| (flaw cof p.puz)
|
|
& %+ (coup cof) (mock [q.vax q.p.puz] (mole ska))
|
|
|= val=*
|
|
`vase`[p.p.puz val]
|
|
==
|
|
|
|
Here we start to get into ford's caching system. We wrap our computation
|
|
in a call to `++clef` so that we only actually compute it if the result
|
|
is not already in our cache. First we'll discuss the computation, then
|
|
we'll discuss the caching system.
|
|
|
|
We call `++mule` with a call to `++mint:ut` on the type of our subject
|
|
vase against the given twig. In other words, we're compiling the twig
|
|
with against the subject type in the given subject vase.
|
|
|
|
If compilation fails, then we produce an error bolt with the produced
|
|
stack trace. Otherwise, we run the produced nock with `++mock` and our
|
|
sky function. We convert the produced toon to a bolt with `++coup` and
|
|
use the type from `puz` combined with the value from `mock` to produce
|
|
our vase.
|
|
|
|
If this process seems harder than just calling `++slap`, it's because it
|
|
is. We have two requirements that `++slap` doesn't satisfy. First, we
|
|
want the to use an explicit sky function for use with `.^`. With
|
|
`++slap`, you get whatever sky function is available in the calling
|
|
context, which in ford is none. Second, we want to explicitly handle the
|
|
stack trace on failure. `++slap` would cause crash on failure.
|
|
|
|
We haven't yet discussed either `++clef` or `++coup`. We'll start with
|
|
`++coup` to finish the discussion of the computation.
|
|
|
|
\``++ coup :: toon to bolt |= cof=cafe |* [ton=toon fun=$+(* *)] :- p=cof ^= q ?- -.ton %2 [%2 p=p.ton] %0 [%0 p=*(set beam) q=(fun p.ton)] %1 ~& [%coup-need ((list path) p.ton)] =- ?- -.faw & [%1 p=(sa (turn p.faw |=(a=beam [a *(list tank)])))] | [%2 p=p.faw] == ^= faw |- ^- (each (list beam) (list tank)) ?~ p.ton [%& ~] =+ nex=$(p.ton t.p.ton) =+ pax=(path i.p.ton) ?~ pax [%| (smyt pax) ?:(?=(& -.nex) ~ p.nex)] =+ zis=(tome t.pax) ?~ zis [%| (smyt pax) ?:(?=(& -.nex) ~ p.nex)] ?- -.nex & [%& u.zis p.nex] | nex == ==`
|
|
|
|
Recall that a toon is either a `%0` value, a `%1` block, or a `%2`
|
|
failure. Converting a `%2` toon failure into a `%2` bolt failure is
|
|
trivial. Converting a `%0` toon value into a `%0` bolt value is easy
|
|
since we assume there were no dependencies. Converting the blocks is
|
|
rather more difficult.
|
|
|
|
To compute `faw`, we recurse through the list of paths in the `%1` toon.
|
|
At each one, we make sure with `++tome` that it is, in fact, a beam. If
|
|
so, then we check to see if the later paths succeed as well. If so, we
|
|
append the current path to the list of other paths. If not, we produce
|
|
the error message we got from processing the rest of the paths. If this
|
|
path is not a beam, then we fail, producing a list of tanks including
|
|
this path and, if later paths fail too, those paths as well.
|
|
|
|
If some paths were not beams, then we produce a `%2` error bolt. If all
|
|
paths were correct, then we produce a `%1` blocking bolt.
|
|
|
|
We will now discuss `++clef`. This is where the cache magic happens.
|
|
|
|
++ clef :: cache a result
|
|
|* sem=*
|
|
|* [hoc=(bolt) fun=(burg)]
|
|
?- -.q.hoc
|
|
%2 hoc
|
|
%1 hoc
|
|
%0
|
|
=^ cux p.hoc ((calk p.hoc) sem q.q.hoc)
|
|
?~ cux
|
|
=+ nuf=(cope hoc fun)
|
|
?- -.q.nuf
|
|
%2 nuf
|
|
%1 nuf
|
|
%0
|
|
:- p=(came p.nuf `calx`[sem `calm`[now p.q.nuf] q.q.hoc q.q.nuf])
|
|
q=q.nuf
|
|
==
|
|
[p=p.hoc q=[%0 p=p.q.hoc q=((calf sem) u.cux)]]
|
|
==
|
|
|
|
If the value is already an error or a block, we just pass that through.
|
|
Otherwise, we look up the request in the cache with `++calk`.
|
|
|
|
++ calk :: cache lookup
|
|
|= a=cafe ::
|
|
|= [b=@tas c=*] ::
|
|
^- [(unit calx) cafe] ::
|
|
=+ d=(~(get by q.a) [b c]) ::
|
|
?~ d [~ a] ::
|
|
[d a(p (~(put in p.a) u.d))] ::
|
|
|
|
When looking up something in the cache, we mark it if we find it. This
|
|
way, we have in our cache the set of all cache entries that have been
|
|
referenced. While we do not at present do anything with this data, it
|
|
should be used to clear out old and unused entries in the cache.
|
|
|
|
Moving on in `++clef`, we check to see if we actually found anything. If
|
|
we didn't find a cache entry, then we run the computation in `fun`, and
|
|
examine its result. If it produced a `%2` error or `%1` block bolt, we
|
|
just pass that through. Otherwise, we produce both the value and an
|
|
updated cache with this new entry. We add the entry with `++came`.
|
|
|
|
++ came ::
|
|
|= [a=cafe b=calx] :: cache install
|
|
^- cafe ::
|
|
a(q (~(put by q.a) [-.b q.b] b)) ::
|
|
|
|
We key cache entries by the type of computation (`-:calx`) and the
|
|
inputs to the computation (`q:calc`). This just puts the cache line in
|
|
the cache at the correct key.
|
|
|
|
Back in `++clef`, if we did find a cache entry, then we just produce the
|
|
value at that cache line. We convert the cache line into a value with
|
|
`++calf`.
|
|
|
|
++ calf :: reduce calx
|
|
|* sem=* :: a typesystem hack
|
|
|= cax=calx
|
|
?+ sem !!
|
|
%hood ?>(?=(%hood -.cax) r.cax)
|
|
%slap ?>(?=(%slap -.cax) r.cax)
|
|
%slam ?>(?=(%slam -.cax) r.cax)
|
|
==
|
|
|
|
This is simply a typesystem hack. Because the `sem` is passed in through
|
|
a wet gate, we know at type time which of the three cases will be
|
|
chosen. Thus, the correct type of the value in the cache line gets
|
|
passed through to the caller. This also depends on the fact that
|
|
`++clef` is wet. The type stuff here is mathematically interesting, but
|
|
the action is simple: we get the value from the cache line.
|
|
|
|
This concludes our discussion of `++clef` and `++maim`.
|
|
|
|
Back in `++abut`, recall that we processed the structures, libraries,
|
|
and body with `++apex`. Then, we slapped our preamble (structures and
|
|
libraries) against zuse with `++maim`. Next, we process our resources
|
|
with `++chap`. Note that we pass in the preamble so that we may refer to
|
|
anything in there in our resources.
|
|
|
|
`++chap` is broken up into a different case for each horn. We'll go
|
|
through them one by one.
|
|
|
|
++ chap :: produce resources
|
|
|= [cof=cafe bax=vase hon=horn]
|
|
^- (bolt vase)
|
|
?- -.hon
|
|
%ape (maim cof bax p.hon)
|
|
|
|
This is `/~`. We slap the twig against our context.
|
|
|
|
%arg
|
|
%+ cope (maim cof bax p.hon)
|
|
|= [cof=cafe gat=vase]
|
|
(maul cof gat !>([how arg]))
|
|
|
|
This is `/$`. We slap the twig against our context, which we expect to
|
|
produce a gate. We slam this gate with a sample of `how` and `arg`,
|
|
which is our location and the heel (virtual path extension).
|
|
|
|
`++maul` is similar to `++maim`, but it slams instead of slaps.
|
|
|
|
++ maul :: slam
|
|
|= [cof=cafe gat=vase sam=vase]
|
|
^- (bolt vase)
|
|
%+ (clef %slam) (fine cof gat sam)
|
|
|= [cof=cafe gat=vase sam=vase]
|
|
=+ top=(mule |.((slit p.gat p.sam)))
|
|
?- -.top
|
|
| (flaw cof p.top)
|
|
& %+ (coup cof) (mong [q.gat q.sam] (mole ska))
|
|
|= val=*
|
|
`vase`[p.top val]
|
|
==
|
|
|
|
We cache slams exactly as we cache slaps. We use `++slit` to find the
|
|
type of the product of the slam given the types of the gate and the
|
|
sample.
|
|
|
|
If this type fails, we produce the given stack trace as a `%2` error
|
|
bolt. Otherwise, we produce the top produced above combined with the
|
|
value we get from slamming the values in the vases with `++mong`.
|
|
|
|
Back to `++chap`.
|
|
|
|
%day (chad cof bax %dr p.hon)
|
|
|
|
This is `/|`. We call `++chad` to convert textual names to relative
|
|
dates and process the next horn against each of the discovered paths.
|
|
|
|
++ chad :: atomic list
|
|
|= [cof=cafe bax=vase doe=term hon=horn]
|
|
^- (bolt vase)
|
|
%+ cope ((lash (slat doe)) cof how)
|
|
|= [cof=cafe yep=(map ,@ span)]
|
|
=+ ^= poy ^- (list (pair ,@ span))
|
|
%+ sort (~(tap by yep) ~)
|
|
|=([a=[@ *] b=[@ *]] (lth -.a -.b))
|
|
%+ cope
|
|
|- ^- (bolt (list (pair ,@ vase)))
|
|
?~ poy (fine cof ~)
|
|
%+ cope $(poy t.poy)
|
|
|= [cof=cafe nex=(list (pair ,@ vase))]
|
|
%+ cope (chap(s.how [q.i.poy s.how]) cof bax hon)
|
|
|= [cof=cafe elt=vase]
|
|
(fine cof [[p.i.poy elt] nex])
|
|
|= [cof=cafe yal=(list (pair ,@ vase))]
|
|
%+ fine cof
|
|
|- ^- vase
|
|
?~ yal [[%cube 0 [%atom %n]] 0]
|
|
(slop (slop [[%atom doe] p.i.yal] q.i.yal) $(yal t.yal))
|
|
|
|
First, we call `++lash` to parse the children of the current beam and
|
|
pick out those ones that are of the requested format.
|
|
|
|
++ lash :: atomic sequence
|
|
|= wox=$+(span (unit ,@))
|
|
|= [cof=cafe bem=beam]
|
|
^- (bolt (map ,@ span))
|
|
%+ cope (lend cof bem)
|
|
|= [cof=cafe arc=arch]
|
|
(fine cof (lark wox arc))
|
|
|
|
First, we get the arch with `++lend`, as described above. We filter and
|
|
parse the child names with `++lark` according to the given parser
|
|
function.
|
|
|
|
In `++chad`, this parser function is `(slat doe)`, which will parse a
|
|
cord into an atom of the requested odor. For `%day` the odor is for
|
|
relative dates.
|
|
|
|
Thus, we now have a map from atoms of the given odor to the actual child
|
|
names. We next turn this map into a list and sort it in increasing order
|
|
by the atom.
|
|
|
|
We next convert this list of pairs of atoms and spans to a list of pairs
|
|
of atoms and vases. We process the given horn once at every child beam,
|
|
producing the resource at that location.
|
|
|
|
Finally, we convert this list of pairs of atoms and vases to a vase of a
|
|
list of pairs of atoms to (well-typed) values. Each entry in the list is
|
|
of type atom with the given odor combined with the type of the produced
|
|
vase.
|
|
|
|
Back in `++chap`, we continue parsing resources.
|
|
|
|
%dub
|
|
%+ cope $(hon q.hon)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof [[%face p.hon p.vax] q.vax])
|
|
|
|
This is `/=`. We process the given horn, giving us a vase. We put as a
|
|
face on the vase so that it may be referred to later by name.
|
|
|
|
%fan
|
|
%+ cope
|
|
|- ^- (bolt (list vase))
|
|
?~ p.hon (fine cof ~)
|
|
%+ cope ^$(hon i.p.hon)
|
|
|= [cof=cafe vax=vase]
|
|
%+ cope ^$(cof cof, p.hon t.p.hon)
|
|
|= [cof=cafe tev=(list vase)]
|
|
(fine cof [vax tev])
|
|
|= [cof=cafe tev=(list vase)]
|
|
%+ fine cof
|
|
|- ^- vase
|
|
?~ tev [[%cube 0 [%atom %n]] 0]
|
|
(slop i.tev $(tev t.tev))
|
|
|
|
This is `/.`. We first process each of the child horns, producing a list
|
|
of vases. This is done by just recursing on `++chap`. Then, we simply
|
|
fold over this list to create a vase of the list of values.
|
|
|
|
%for $(hon q.hon, s.how (weld (flop p.hon) s.how))
|
|
|
|
This is `/,`. We simply recurse on the horn with the given path welded
|
|
onto our current beam.
|
|
|
|
%hub (chad cof bax %ud p.hon)
|
|
|
|
This is `/@`. This is exactly like the processing of `%day` except we
|
|
expect the children to be named as unsigned integers rather than
|
|
relative dates. We process the horn at each of the children's locations
|
|
and produce a list of pairs of absolute dates and values.
|
|
|
|
%man
|
|
|- ^- (bolt vase)
|
|
?~ p.hon (fine cof [[%cube 0 [%atom %n]] 0])
|
|
%+ cope $(p.hon l.p.hon)
|
|
|= [cof=cafe lef=vase]
|
|
%+ cope ^$(cof cof, p.hon r.p.hon)
|
|
|= [cof=cafe rig=vase]
|
|
%+ cope ^^^$(cof cof, hon q.n.p.hon)
|
|
|= [cof=cafe vax=vase]
|
|
%+ fine cof
|
|
%+ slop
|
|
(slop [[%atom %tas] p.n.p.hon] vax)
|
|
(slop lef rig)
|
|
|
|
This is `/*`. We process each of the horns in the given map by recursion
|
|
through `++chap`. Once we have these vases, we create a vase of a map
|
|
from the given textual names to the produced values.
|
|
|
|
%now (chad cof bax %da p.hon)
|
|
|
|
This is `/&`. This is exactly like the processing of `%now` except we
|
|
expect the children to be names as absolute dates rather than relative
|
|
dates. We process the horn at each of the children's locations and
|
|
produce a list of pairs of absolute dates and values.
|
|
|
|
%nap (chai cof bax p.hon)
|
|
|
|
This is `/%`. Here, we process the horn at each of our children with
|
|
`++chai`.
|
|
|
|
++ chai :: atomic map
|
|
|= [cof=cafe bax=vase hon=horn]
|
|
^- (bolt vase)
|
|
%+ cope (lend cof how)
|
|
|= [cof=cafe arc=arch]
|
|
%+ cope
|
|
|- ^- (bolt (map ,@ vase))
|
|
?~ r.arc (fine cof ~)
|
|
%+ cope $(r.arc l.r.arc)
|
|
|= [cof=cafe lef=(map ,@ vase)]
|
|
%+ cope `(bolt (map ,@ vase))`^$(cof cof, r.arc r.r.arc)
|
|
|= [cof=cafe rig=(map ,@ vase)]
|
|
%+ cope (chap(s.how [p.n.r.arc s.how]) cof bax hon)
|
|
|= [cof=cafe nod=vase]
|
|
(fine cof [[p.n.r.arc nod] lef rig])
|
|
|= [cof=cafe doy=(map ,@ vase)]
|
|
%+ fine cof
|
|
|- ^- vase
|
|
?~ doy [[%cube 0 [%atom %n]] 0]
|
|
%+ slop
|
|
(slop [[%atom %a] p.n.doy] q.n.doy)
|
|
(slop $(doy l.doy) $(doy r.doy))
|
|
|
|
We get the arch at our current beam with `++lend`. Then, we process the
|
|
horn at each of our children to give us a map of atoms to vases.
|
|
Finally, we convert that into a vase of a map of these atoms to the
|
|
values. This is very similar to `++chad` and the handling of `%man`.
|
|
|
|
%see $(hon q.hon, how p.hon)
|
|
|
|
This is `/:`. We process the given horn at the given beam.
|
|
|
|
%saw
|
|
%+ cope $(hon q.hon)
|
|
|= [cof=cafe sam=vase]
|
|
%+ cope (maim cof bax p.hon)
|
|
|= [cof=cafe gat=vase]
|
|
(maul cof gat sam)
|
|
|
|
This is `/;`. First, we process the given horn. Then, we slap the given
|
|
twig against our context to produce (hopefully) a gate. Finally, we slam
|
|
the vase we got from processing the horn against the gate.
|
|
|
|
%sic
|
|
%+ cope $(hon q.hon)
|
|
|= [cof=cafe vax=vase]
|
|
%+ cope (maim cof bax [%bctr p.hon])
|
|
|= [cof=cafe tug=vase]
|
|
?. (~(nest ut p.tug) | p.vax)
|
|
(flaw cof [%leaf "type error: {<p.hon>} {<q.hon>}"]~)
|
|
(fine cof [p.tug q.vax])
|
|
|
|
This is `/^`. First, we process the given horn. Then, we slap the the
|
|
bunt of the given tile against our context. This will produce a vase
|
|
with the correct type. We test to see if this type nests within the type
|
|
of the vase we got from processing the horn. If so, we produce the value
|
|
from the horn along with the type from the tile. Otherwise, we produce a
|
|
`%2` error bolt.
|
|
|
|
%toy (cope (make cof %bake p.hon how ~) feel)
|
|
==
|
|
|
|
This is `/mark/`. Here, we simply run the `%bake` silk on the given
|
|
mark, producing a cage. We convert this cage into a vase with `++feel`,
|
|
which is exactly as simple as it sounds like it should be.
|
|
|
|
++ feel |=([a=cafe b=cage] (fine a q.b)) :: cage to vase
|
|
|
|
This is trivial.
|
|
|
|
We will discuss later `++make` and how `%bake` is processed. Suffice it
|
|
to say that baking a resource with a given mark gets the resource and
|
|
converts it, if necessary, to the requested mark.
|
|
|
|
This concludes our discussion of `++chap`.
|
|
|
|
We return once more to `++abut`.
|
|
|
|
++ abut :: generate
|
|
|= [cof=cafe hyd=hood]
|
|
^- (bolt vase)
|
|
%+ cope (apex cof hyd)
|
|
|= [cof=cafe sel=_..abut]
|
|
=. ..abut sel
|
|
%+ cope (maim cof pit able)
|
|
|= [cof=cafe bax=vase]
|
|
%+ cope (chap cof bax [%fan fan.hyd])
|
|
|= [cof=cafe gox=vase]
|
|
%+ cope (maim cof (slop gox bax) [%tssg (flop boy)])
|
|
|= [cof=cafe fin=vase]
|
|
(fine cof fin)
|
|
|
|
Recall that we processed our structures, libraries and body with
|
|
`++apex`. We slapped our structures and libraries against zuse with
|
|
`++maim`. We processed our resources with `++chap`. Now, all our body
|
|
twigs are collected in a `=~` and slapped against our structures,
|
|
libraries, and resources. This produces our final result.
|
|
|
|
The hook file has been assembled. And there was great rejoicing.
|
|
|
|
Lifecycle of a Kiss
|
|
-------------------
|
|
|
|
We're now going to go through a series of lifecycle descriptions. When a
|
|
user of ford sends a kiss, it is one of a dozen different types of silk.
|
|
We'll go through each one, tracing through the flow of control of each
|
|
of these.
|
|
|
|
First, though, we'll describe the common handling to all kisses.
|
|
|
|
The silk in a `%exec` kiss to ford ends up in `++apex`, so we'll enter
|
|
the narrative here.
|
|
|
|
++ apex :: call
|
|
|= kus=(unit silk)
|
|
^+ +>
|
|
?~ kus
|
|
=+ nym=(~(get by dym.bay) hen)
|
|
?~ nym :: XX should never
|
|
~& [%ford-mystery hen]
|
|
+>.$
|
|
=+ tas=(need (~(get by q.tad.bay) u.nym))
|
|
amok:~(camo zo [u.nym tas])
|
|
=+ num=p.tad.bay
|
|
?< (~(has by dym.bay) hen)
|
|
=: p.tad.bay +(p.tad.bay)
|
|
dym.bay (~(put by dym.bay) hen num)
|
|
==
|
|
~(exec zo [num `task`[hen u.kus 0 ~]])
|
|
|
|
Recall that a `%exec` kiss actually sends a unit silk. If it's null,
|
|
we're trying to cancel the request. We first look up the task number
|
|
keyed by duct. If we don't find it, then we're trying to cancel a
|
|
request that either was never started or has already completed. We print
|
|
out `%ford-mystery` and do nothing. If we do find the task number, then
|
|
we look up the task from it, call `++camo:zo` to cancel pending
|
|
requests, and call `++amok:zo` to remove the task from our task lists.
|
|
|
|
++ camo :: stop requests
|
|
^+ .
|
|
=+ kiz=(~(tap by q.kig) *(list ,[p=@ud q=beam]))
|
|
|- ^+ +>
|
|
?~ kiz +>
|
|
%= $
|
|
kiz t.kiz
|
|
mow :_ mow
|
|
:- hen
|
|
:^ %pass [(scot %p our) (scot %ud num) (scot %ud p.i.kiz) ~]
|
|
%c
|
|
[%warp [our p.q.i.kiz] q.q.i.kiz ~]
|
|
==
|
|
|
|
Our list of blocks is in `q.kig`, so we iterate over it, cancelling our
|
|
pending requests for each block. Our requests are all to clay, so we
|
|
need only to send `%warp` kisses with a null instead of a rave.
|
|
|
|
++ amok
|
|
%_ ..zo
|
|
q.tad.bay (~(del by q.tad.bay) num)
|
|
dym.bay (~(del by dym.bay) nah)
|
|
==
|
|
|
|
We remove the task number from the map of numbers to tasks and the duct
|
|
from the map of ducts to task numbers.
|
|
|
|
Back in `++apex`, if we were given a silk, we need to process it. We add
|
|
the task to our maps, increment the next task number, and call
|
|
`++exec:zo` on the new task.
|
|
|
|
++ exec :: execute app
|
|
^+ ..zo
|
|
?: !=(~ q.kig) ..zo
|
|
|- ^+ ..zo
|
|
=+ bot=(make [~ jav.bay] kas)
|
|
=. ..exec (dash p.bot)
|
|
?- -.q.bot
|
|
%0 amok:(expo [%made %& p.q.bot q.q.bot])
|
|
%2 amok:(expo [%made %| p.q.bot])
|
|
%1 =+ zuk=(~(tap by p.q.bot) ~)
|
|
=< abet
|
|
|- ^+ ..exec
|
|
?~ zuk ..exec
|
|
=+ foo=`_..exec`(camp %x `beam`p.i.zuk)
|
|
$(zuk t.zuk, ..exec foo)
|
|
==
|
|
|
|
If we're still blocked on something in `q.kig`, we don't do anything.
|
|
|
|
Otherwise, we try to process the silk with `++make`. `++make` handles
|
|
each individual request and will be the entire focus of the remainder of
|
|
this doc after this section. It produces a bolt of a cage.
|
|
|
|
We put the new cache in our state with `++dash`.
|
|
|
|
++ dash :: process cache
|
|
|= cof=cafe
|
|
^+ +>
|
|
%_(+> jav.bay q.cof)
|
|
|
|
The cache is put in the baby so that it gets stored across calls to
|
|
ford.
|
|
|
|
In `++exec`, we process the bolt in three different ways according to
|
|
the type of bolt produced. If we produced a `%0` value bolt, we use
|
|
`++expo` to give the produced value and set of dependencies as a `%made`
|
|
gift, and we remove ourselves from the task list with `++amok`.
|
|
|
|
++ expo :: return gift
|
|
|= gef=gift
|
|
%_(+> mow :_(mow [hen %give gef]))
|
|
|
|
We simply push the gift onto our list of moves.
|
|
|
|
In `++exec`, if we produced a `%2` error bolt, we produce a `%made` gift
|
|
with the stack trace.
|
|
|
|
If we produced a `%1` block bolt, we iterate through each of the blocks
|
|
and call `++camp` to produce a clay request for the resource.
|
|
|
|
++ camp :: request a file
|
|
|= [ren=care bem=beam]
|
|
^+ +>
|
|
%= +>
|
|
kig [+(p.kig) (~(put by q.kig) p.kig bem)]
|
|
mow :_ mow
|
|
:- hen
|
|
:^ %pass [(scot %p our) (scot %ud num) (scot %ud p.kig) ~]
|
|
%c
|
|
[%warp [our p.bem] q.bem [~ %& %x r.bem s.bem]]
|
|
==
|
|
|
|
We put the resource in our block list in `q.kig` so that we save the
|
|
fact that we're blocked. We then produce the `%warp` request to clay for
|
|
the resource. Our request path has the format
|
|
\`/[our-ship]/[task-number]/[block-number]'.
|
|
|
|
We'll now describe how each of the individual silks are processed in
|
|
`++make`.
|
|
|
|
Lifecycle of a Cell
|
|
-------------------
|
|
|
|
^
|
|
%. [cof p.kas q.kas]
|
|
;~ cope
|
|
;~ coax
|
|
|=([cof=cafe p=silk q=silk] ^$(cof cof, kas p.kas))
|
|
|=([cof=cafe p=silk q=silk] ^$(cof cof, kas q.kas))
|
|
==
|
|
::
|
|
|= [cof=cafe bor=cage heg=cage] ^- (bolt cage)
|
|
[p=cof q=[%0 ~ [%$ (slop q.bor q.heg)]]]
|
|
==
|
|
|
|
Silks autocons. The product of a cell of silks is a cell of the products
|
|
of the silks, so we evaluate the two silks in parallel with `++coax` and
|
|
slop together the results in a cell vase. We mark the product with `%$`,
|
|
which means we know no more mark information than that it is a noun.
|
|
|
|
++ coax :: bolt across
|
|
|* [hoc=(bolt) fun=(burg)]
|
|
?- -.q.hoc
|
|
%0 =+ nuf=$:fun(..+<- p.hoc)
|
|
:- p=p.nuf
|
|
^= q
|
|
?- -.q.nuf
|
|
%0 [%0 p=(grom p.q.hoc p.q.nuf) q=[q.q.hoc q.q.nuf]]
|
|
%1 q.nuf
|
|
%2 q.nuf
|
|
==
|
|
%1 =+ nuf=$:fun(..+<- p.hoc)
|
|
:- p=p.nuf
|
|
^= q
|
|
?- -.q.nuf
|
|
%0 q.hoc
|
|
%1 [%1 p=(grom p.q.nuf p.q.hoc)]
|
|
%2 q.nuf
|
|
==
|
|
%2 hoc
|
|
==
|
|
|
|
If the first bolt is a value, we evaluate the burg to get the next bolt.
|
|
If that also produces a value, we merge the dependency sets and produce
|
|
a cell of the two values. Otherwise, we produce the block or error of
|
|
the second bolt.
|
|
|
|
If the first bolt is a block, we evaluate the burg to get the next bolt.
|
|
If that produces a value, we just produce the block. If it produces a
|
|
block, we merge the two block sets. If it produces an error, we produce
|
|
that error.
|
|
|
|
If the first bolt is already an error, we just pass that through.
|
|
|
|
Note that `++coax` (and, indeed, `++cope`) is reasonable to use with
|
|
`;~`.
|
|
|
|
Lifecycle of a `%bake`
|
|
----------------------
|
|
|
|
%bake
|
|
%+ cool |.(leaf/"ford: bake {<p.kas>} {<(tope q.kas)>}")
|
|
%+ cope (lima cof p.kas q.kas r.kas)
|
|
|= [cof=cafe vux=(unit vase)]
|
|
?~ vux
|
|
(flaw cof (smyt (tope q.kas)) ~)
|
|
(fine cof [p.kas u.vux])
|
|
|
|
This is one of the most critical silks. We are going to functionally
|
|
produce the hook file at the given beam with the given heel. The result
|
|
will be of the correct mark, even if we need to run conversion
|
|
functions. The functionality is encapsulated in `++lime`. If it produces
|
|
null, then we produce an error. Otherwise, we take the vase produced and
|
|
give it the correct mark.
|
|
|
|
++ lima :: load at depth
|
|
|= [cof=cafe for=mark bem=beam arg=heel]
|
|
^- (bolt (unit vase))
|
|
%+ cope (lend cof bem)
|
|
|= [cof=cafe arc=arch]
|
|
^- (bolt (unit vase))
|
|
?: (~(has by r.arc) for)
|
|
(lace cof for bem(s [for s.bem]) arg)
|
|
=+ haz=(turn (~(tap by r.arc) ~) |=([a=@tas b=~] a))
|
|
?~ haz (fine cof ~)
|
|
%+ cope (lion cof for -.bem haz)
|
|
|= [cof=cafe wuy=(unit (list ,@tas))]
|
|
?~ wuy (fine cof ~)
|
|
?> ?=(^ u.wuy)
|
|
%+ cope (make cof %bake i.u.wuy bem arg)
|
|
|= [cof=cafe hoc=cage]
|
|
%+ cope (lope cof i.u.wuy t.u.wuy -.bem q.hoc)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof ~ vax)
|
|
|
|
First, we load the arch at the given beam with `++lend`. If we have a
|
|
child named the mark, our job is straightforward, so we go ahead and
|
|
load that with `++lace`.
|
|
|
|
Otherwise, we iterate through our children. If we have no children, we
|
|
produce null, signifying that we didn't find any way to convert to the
|
|
requested mark. Otherwise, we call `++lion` to find a translation path
|
|
from one of the available marks into the target mark. We recursively
|
|
bake the child that has a path to the target mark, and then we call
|
|
`++lope` to translate this mark into the target mark.
|
|
|
|
We'll first discuss the direct case of when one of our children is of
|
|
the correct mark.
|
|
|
|
++ lace :: load and check
|
|
|= [cof=cafe for=mark bem=beam arg=heel]
|
|
^- (bolt (unit vase))
|
|
=+ bek=`beak`[p.bem q.bem r.bem]
|
|
%+ cope (lend cof bem)
|
|
|= [cof=cafe arc=arch]
|
|
?^ q.arc
|
|
(cope (cope (liar cof bem) (lake for bek)) fest)
|
|
?: (~(has by r.arc) %hook)
|
|
%+ cope (fade cof %hook bem)
|
|
|= [cof=cafe hyd=hood]
|
|
(cope (cope (abut:(meow bem arg) cof hyd) (lake for bek)) fest)
|
|
(fine cof ~)
|
|
|
|
First, we get the arch at the given beam with `++lend`. If this is a
|
|
file, we load the file with `++liar` and coerce the type with `++lake`.
|
|
Otherwise, we check to see if we have a hook file here. If so, we parse
|
|
it with `++fade`, compile it with `++abut:meow`, and coerce the type
|
|
with `++lake`.
|
|
|
|
Otherwise, there is no way to translate this, so we produce null.
|
|
|
|
`++fest` is one line, so we'll get that one out of the way first.
|
|
|
|
++ fest |*([a=cafe b=*] (fine a [~ u=b])) :: bolt to unit
|
|
|
|
This is just `++some` for bolts.
|
|
|
|
We've delayed the discussion of `++fade` far too many times. It's not
|
|
complicated, we just wanted to spare a premature discussion of `++make`
|
|
and the `%bake` silk. We 're now able to discuss everything in `++fade`
|
|
with ease.
|
|
|
|
++ fade :: compile to hood
|
|
|= [cof=cafe for=mark bem=beam]
|
|
^- (bolt hood)
|
|
%+ cool |.(leaf/"ford: fade {<[(tope bem)]>}")
|
|
%+ cope (make cof [%bake for bem ~])
|
|
|= [cof=cafe cay=cage]
|
|
%+ (clef %hood) (fine cof bem cay)
|
|
^- (burg (pair beam cage) hood)
|
|
|= [cof=cafe bum=beam cay=cage]
|
|
=+ rul=(fair bem)
|
|
?. ?=(@ q.q.cay)
|
|
(flaw cof ~)
|
|
=+ vex=((full rul) [[1 1] (trip q.q.cay)])
|
|
?~ q.vex
|
|
(flaw cof [%leaf "syntax error: {<p.p.vex>} {<q.p.vex>}"] ~)
|
|
(fine cof p.u.q.vex)
|
|
|
|
We first push a line onto a stack trace to say that we're parsing into a
|
|
hood file.
|
|
|
|
We bake the given beam with the given mark and no heel. Recall that
|
|
baking gate, core, door, hoon, and hook files produces simply an atom of
|
|
the text. We check to make sure that our value is an atom, failing
|
|
otherwise.
|
|
|
|
The parsing step is run within `++clef` so that the result is cached. We
|
|
call `++fair` with the current beam to generate the parsing rule, and we
|
|
parse the file. If parsing fails, we fail giving a syntax error with the
|
|
line and column number. Otherwise, we produce the value.
|
|
|
|
++ liar :: load vase
|
|
|= [cof=cafe bem=beam]
|
|
^- (bolt vase)
|
|
=+ von=(ska %cx (tope bem))
|
|
?~ von
|
|
[p=*cafe q=[%1 [[bem ~] ~ ~]]]
|
|
?~ u.von
|
|
(flaw cof (smyt (tope bem)) ~)
|
|
(fine cof ?^(u.u.von [%cell %noun %noun] [%atom %$]) u.u.von)
|
|
|
|
This takes a beam and loads the file at that location. If our sky
|
|
function produces null, that means the resource is currently
|
|
unavailable, so we block on it. If it produces `[~ ~]`, that means our
|
|
resource is permanently unavailable, so we produce an error. Otherwise,
|
|
we produce the value there with a type of either a cell of two nouns or
|
|
an atom, depending on whether the value is a cell or not.
|
|
|
|
Back in `++lima`, recall that we call `++lion` to find a translation
|
|
path.
|
|
|
|
++ lion :: translation search
|
|
|= [cof=cafe too=@tas bek=beak fro=(list ,@tas)]
|
|
^- (bolt (unit (list ,@tas)))
|
|
=| war=(set ,@tas)
|
|
=< -:(apex (fine cof fro))
|
|
|%
|
|
++ apex
|
|
|= rof=(bolt (list ,@tas))
|
|
^- [(bolt (unit (list ,@tas))) _+>]
|
|
?. ?=(%0 -.q.rof) [rof +>.$]
|
|
?~ q.q.rof
|
|
[[p.rof [%0 p.q.rof ~]] +>.$]
|
|
=^ orf +>.$ (apse cof i.q.q.rof)
|
|
?. ?=(%0 -.q.orf)
|
|
[orf +>.$]
|
|
?~ q.q.orf
|
|
$(cof p.orf, q.q.rof t.q.q.rof)
|
|
[[p.orf [%0 (grom p.q.rof p.q.orf) q.q.orf]] +>.$]
|
|
::
|
|
++ apse
|
|
|= [cof=cafe for=@tas]
|
|
^- [(bolt (unit (list ,@tas))) _+>]
|
|
?: =(for too)
|
|
[(fine cof [~ too ~]) +>.$]
|
|
?: (~(has in war) for) [(fine cof ~) +>]
|
|
=. war (~(put in war) for)
|
|
=^ hoc +>.$ (apex (lily cof for bek))
|
|
:_ +>.$
|
|
%+ cope hoc
|
|
|= [cof=cafe ked=(unit (list ,@tas))]
|
|
(fine cof ?~(ked ~ [~ for u.ked]))
|
|
--
|
|
|
|
At a high level, we have `++apex` and `++apse`. `++apex` takes a list of
|
|
marks to try in succession until we find one that can be translated into
|
|
the target mark. On each one, it calls `++apse`, which takes a single
|
|
mark and tries to find a translation path from this mark to the target.
|
|
To do this, it sees which marks we know how to directly translate to,
|
|
and calls `++apex` on this list. The result of this mututal recursion is
|
|
a depth-first search of the translation graph to find the target mark.
|
|
Since the translation graph is not necessarily acyclic, we maintain a
|
|
set of marks that we've already tried.
|
|
|
|
We kick off our search in `++apex`, starting with the given initial list
|
|
of marks that we know how to get to.
|
|
|
|
If `++apex` is called with a bolt other than a `%0` value bolt, we
|
|
simply produce it. Otherwise, we check to see if the list of available
|
|
marks to investigate is null. If so, then we're done, so we produce a
|
|
`%0` bolt with a null list of accessible marks.
|
|
|
|
Otherwise, we process this next mark with `++apse`, which will produce a
|
|
possible list of marks from this one to the target mark. If it fails to
|
|
produce a `%0` bolt, we just produce that. Otherwise, if it produces
|
|
null, we can't get to our target through this mark, so we move on to the
|
|
next one.
|
|
|
|
If it doesn't produce null, then we have successfully found a
|
|
translation path, so we produce it.
|
|
|
|
In `++apse`, we first test to see if we've arrived at the target path.
|
|
If so, we're done, so we produce a list including just ourself.
|
|
Otherwise, we check to see if we've already tried this mark. If so, we
|
|
know we can't succeed here, so we produce null. Otherwise, we put
|
|
ourselves in the set of already-tried marks, and we move on.
|
|
|
|
We call `++lily` to get the list of marks we can translate this one
|
|
into.
|
|
|
|
++ lily :: translation targets
|
|
|= [cof=cafe for=mark bek=beak]
|
|
^- (bolt (list ,@tas))
|
|
=+ raf=(fang cof for bek)
|
|
?: =(%2 -.q.raf) (fine p.raf ~)
|
|
%+ cope raf
|
|
|= [cof=cafe vax=vase]
|
|
%+ fine cof
|
|
%+ weld
|
|
^- (list ,@tas)
|
|
?. (slab %garb p.vax) ~
|
|
=+ gav=((soft (list ,@tas)) q:(slap vax [%cnzy %garb]))
|
|
?~(gav ~ u.gav)
|
|
?. (slab %grow p.vax) ~
|
|
=+ gow=(slap vax [%cnzy %grow])
|
|
(sloe p.gow)
|
|
|
|
We call `++fang` to get the mark definition door. This is documented
|
|
under `%vale`. If getting the mark fails, we produce null because we
|
|
can't translate a non-existent mark into anything.
|
|
|
|
Otherwise, we examine the door. The door may have a `++garb`, which is
|
|
simply a list of marks which know how to translate from the current one.
|
|
There must be a corresponding `++grab` in the definition of the other
|
|
mark, though we don't check that here.
|
|
|
|
The door may also have a `++grow`, which defines how to translate this
|
|
mark into another one. Each arm in `++grow` is the name of a mark we can
|
|
translate into. The call to `++sloe` simply produces a list of arm names
|
|
in `++grow`.
|
|
|
|
Back in `++apse:lion`, we take the list of translation targets we just
|
|
found and call `++apex` on it. If we got back a null, we produce a null;
|
|
otherwise, we produce the list of marks we got back plus the current
|
|
mark.
|
|
|
|
This concludes our discussion of `++lion`.
|
|
|
|
The final piece of `++lima` is `++lope`, which performs the actual
|
|
translation along the path we just computed.
|
|
|
|
++ lope :: translation pipe
|
|
|= [cof=cafe for=mark yaw=(list mark) bek=beak vax=vase]
|
|
^- (bolt vase)
|
|
?~ yaw (fine cof vax)
|
|
%+ cope (link cof i.yaw for bek vax)
|
|
|= [cof=cafe yed=vase]
|
|
^$(cof cof, for i.yaw, yaw t.yaw, vax yed)
|
|
|
|
We iterate through our list, calling `++link` on every adjacent pair of
|
|
marks, translating from one mark to the next until we finish the list of
|
|
marks. A call to `++link` is equivalent to a `%cast` silk, so we
|
|
document it there. After we've called performed every step in the
|
|
translation pipeline, we're done.
|
|
|
|
Lifecycle of a `%boil`
|
|
----------------------
|
|
|
|
%boil
|
|
%+ cool |.(leaf/"ford: boil {<p.kas>} {<(tope q.kas)>} {<r.kas>}")
|
|
%+ cope (lamp cof q.kas)
|
|
|= [cof=cafe bem=beam]
|
|
%+ cope (lime cof p.kas bem r.kas)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof `cage`[p.kas vax])
|
|
|
|
At a high level, we try to bake at the given beam, and if it fails, we
|
|
go up a level and try again. This is the usual semantics of ford, and
|
|
this should nearly always be preferred over directly baking.
|
|
|
|
First, we normalize the version case to a number with `++lamp`. This
|
|
allows caching to be based on revision number rather than something more
|
|
ephemeral like a particular time.
|
|
|
|
++ lamp :: normalize version
|
|
|= [cof=cafe bem=beam]
|
|
^- (bolt beam)
|
|
=+ von=(ska %cw (tope bem(s ~)))
|
|
?~ von [p=cof q=[%1 [bem ~] ~ ~]]
|
|
(fine cof bem(r [%ud ((hard ,@) (need u.von))]))
|
|
|
|
We call the sky function with `%cw`, asking clay for the revision number
|
|
at this case. If the case refers to a revision that isn't there yet, we
|
|
produce a `%1` blocking bolt. Otherwise, we require that the value exist
|
|
and that it's a number, both of which are guaranteed by clay. We produce
|
|
this number.
|
|
|
|
Next for `%boil` we call `++lime` to try to load the beam.
|
|
|
|
++ lime :: load beam
|
|
|= [cof=cafe for=mark bem=beam arg=heel]
|
|
=+ [mob=bem mer=(flop arg)]
|
|
|- ^- (bolt vase)
|
|
%+ cope (lima cof for mob (flop mer))
|
|
|= [cof=cafe vux=(unit vase)]
|
|
?^ vux (fine cof u.vux)
|
|
?~ s.mob
|
|
(flaw cof (smyt (tope bem)) ~)
|
|
^$(s.mob t.s.mob, mer [i.s.mob mer])
|
|
|
|
We start at the given beam and try to bake it. If it succeeds, we're
|
|
good. Otherwise, we pop off the top level of the path and put it in our
|
|
heel (virtual path extension). We do this recursively until either we
|
|
find something we can bake or we've gone all the way up to the root path
|
|
of the desk, in which case we fail.
|
|
|
|
Lifecycle of a `%call`
|
|
----------------------
|
|
|
|
%call
|
|
%+ cool |.(leaf/"ford: call {<`@p`(mug kas)>}")
|
|
%. [cof p.kas q.kas]
|
|
;~ cope
|
|
;~ coax
|
|
|=([cof=cafe p=silk q=silk] ^$(cof cof, kas p))
|
|
|=([cof=cafe p=silk q=silk] ^$(cof cof, kas q))
|
|
==
|
|
::
|
|
|= [cof=cafe gat=cage sam=cage]
|
|
(maul cof q.gat q.sam)
|
|
::
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof %noun vax)
|
|
==
|
|
|
|
This is slam for silks. We process both of the given silks in parallel
|
|
with `++coax`. We then slam the two produced vases together with
|
|
`++maul` and mark the produced vase with `%noun` since we don't know any
|
|
more specific mark.
|
|
|
|
`++coax` is documented under Lifecycle of a Cell.
|
|
|
|
Lifecycle of a `%cast`
|
|
----------------------
|
|
|
|
%cast
|
|
%+ cool |.(leaf/"ford: cast {<p.kas>}")
|
|
%+ cope $(kas q.kas)
|
|
|= [cof=cafe cay=cage]
|
|
%+ cope (link cof p.kas p.cay [our %main %da now] q.cay)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof [p.kas vax])
|
|
|
|
This is a request to convert data of one mark to another mark directly.
|
|
We evaluate the given silk and pass the result into `++link`, which
|
|
performs the actual translation. Note that this will not search for
|
|
indirect conversion paths, so the conversion must be defined either in
|
|
the `++grow` of the given mark or the `++grab` of the target mark.
|
|
|
|
++ link :: translate
|
|
|= [cof=cafe too=mark for=mark bek=beak vax=vase]
|
|
^- (bolt vase)
|
|
?: =(too for) (fine cof vax)
|
|
?: |(=(%noun for) =(%$ for))
|
|
((lake too bek) cof vax)
|
|
%+ cope (fang cof for bek)
|
|
|= [cof=cafe pro=vase]
|
|
?: &((slab %grow p.pro) (slab too p:(slap pro [%cnzy %grow])))
|
|
%+ cope (keel cof pro [[%& 6]~ vax]~)
|
|
|= [cof=cafe pox=vase]
|
|
(maim cof pox [%tsgr [%cnzy %grow] [%cnzy too]])
|
|
%+ cope (fang cof too bek)
|
|
|= [cof=cafe pro=vase]
|
|
=+ ^= zat ^- (unit vase)
|
|
?. (slab %grab p.pro) ~
|
|
=+ gab=(slap pro [%cnzy %grab])
|
|
?. (slab for p.gab) ~
|
|
`(slap gab [%cnzy for])
|
|
?~ zat
|
|
(flaw cof [%leaf "ford: no link: {<[for too]>}"]~)
|
|
(maul cof u.zat vax)
|
|
|
|
This performs one step in the translation pipeline. If the given and
|
|
target marks are the same, we're done. If we're translating from a noun
|
|
or the empty mark, we coerce with `++lake` (documented in `%vale`).
|
|
Otherwise, we're translating from a user-defined mark.
|
|
|
|
We load the definition of the given mark with `++fang`, and we check to
|
|
see if it has an arm in `++grow` named the target mark. If so, we place
|
|
our data in the sample of the door with `++keel` and slap the arm.
|
|
`++keel` is equivalent to a `%mute` silk, so we document it there.
|
|
|
|
If there is no arm in `++grow` of the given mark named the target mark,
|
|
we suppose there must be an arm in `++grab` of the target mark named the
|
|
given mark. We get the definition of the target mark and check to see if
|
|
it has the required arm, failing if it doesn't. Finally, we slam the
|
|
data against the correct arm, producing the translated data.
|
|
|
|
If you're confused as to why the handling of `++grow` and `++grab` look
|
|
superficially so different, remember that the correct arm in `++grow`
|
|
does not have a sample while the one in `++grab` does. This means they
|
|
must be called rather differently.
|
|
|
|
Lifecycle of a `%diff`
|
|
----------------------
|
|
|
|
%diff
|
|
%+ cool |.(leaf/"ford: diff {<`@p`(mug p.kas)>} {<`@p`(mug q.kas)>}")
|
|
(diff cof p.kas q.kas)
|
|
|
|
We push debug information onto the trace and go right to `++diff`.
|
|
|
|
++ diff
|
|
|= [cof=cafe kas=silk kos=silk]
|
|
^- (bolt cage)
|
|
%. [cof kas kos]
|
|
;~ cope
|
|
;~ coax
|
|
|=([cof=cafe p=silk q=silk] (make cof p))
|
|
|=([cof=cafe p=silk q=silk] (make cof q))
|
|
==
|
|
|= [cof=cafe cay=cage coy=cage]
|
|
|
|
First, we process the two given silks to get our arguments.
|
|
|
|
?. =(p.cay p.coy)
|
|
%+ flaw cof :_ ~
|
|
leaf/"diff on data of different marks: {(trip p.cay)} {(trip p.coy)}"
|
|
|
|
If the two cages have different marks, then we can't diff them, so we
|
|
complain.
|
|
|
|
%+ cope (fang cof p.cay [our %main %da now])
|
|
|= [cof=cafe pro=vase]
|
|
|
|
We pull in the relevant mark's definition.
|
|
|
|
?. (slab %grad p.pro)
|
|
(flaw cof leaf/"no ++grad" ~)
|
|
=+ gar=(slap pro [%cnzy %grad])
|
|
?. (slab %form p.gar)
|
|
?. (slab %sted p.gar)
|
|
(flaw cof leaf/"no ++form:grad nor ++sted:grad" ~)
|
|
=+ for=((soft ,@tas) q:(slap gar [%cnzy %sted]))
|
|
?~ for
|
|
(flaw cof leaf/"bad ++sted:grad" ~)
|
|
(make cof %diff [%cast u.for kas] [%cast u.for kos])
|
|
|
|
If there's no `++grad`, we complain. If there's no `++form:grad`, then
|
|
we look for a `++sted:grad`. If we can't find either, or if
|
|
`++sted:grad` isn't a term, then we complain. If `++sted:grad` exists
|
|
and is a term, then it represents the mark we should use as a proxy to
|
|
get our diff. So, we cast both our given cages to the new mark and start
|
|
the dance again.
|
|
|
|
?. (slab %diff p.gar)
|
|
(flaw cof leaf/"no ++diff:grad" ~)
|
|
|
|
Otherwise, we expect a `++diff:grad`.
|
|
|
|
%+ cope (keel cof pro [[%& 6]~ q.cay]~)
|
|
|= [cof=cafe pox=vase]
|
|
|
|
We put the first cage's data into the sample of the given mark's
|
|
definition.
|
|
|
|
%+ cope
|
|
%^ maul cof
|
|
(slap (slap pox [%cnzy %grad]) [%cnzy %diff])
|
|
q.coy
|
|
|= [cof=cafe dif=vase]
|
|
|
|
We run `++diff:grad` with a sample of the second cage's data.
|
|
|
|
=+ for=((soft ,@tas) q:(slap gar [%cnzy %form]))
|
|
?~ for
|
|
(flaw cof leaf/"bad ++form:grad" ~)
|
|
(fine cof u.for dif)
|
|
==
|
|
|
|
We check that `++form:grad` exists, and we tag the result with it to
|
|
give the final cage.
|
|
|
|
Lifecycle of a `%done`
|
|
----------------------
|
|
|
|
%done [cof %0 p.kas q.kas]
|
|
|
|
This is trivial. We simply produce the given cage with the given set of
|
|
dependencies. This is used when we already have a cage that we want to
|
|
insert into another silk that requires a silk argument. It's analogous
|
|
to the return operator in a monad -- which makes it sound way more
|
|
complicated than it is.
|
|
|
|
Lifecycle of a `%dude`
|
|
----------------------
|
|
|
|
%dude (cool |.(p.kas) $(kas q.kas))
|
|
|
|
This simply puts a given tank on the stack trace if the given silk
|
|
produces an error. This is implemented as a simple call to `++cool`.
|
|
|
|
Lifecycle of a `%dune`
|
|
----------------------
|
|
|
|
%dune
|
|
?~ q.kas [cof [%2 [%leaf "no data"]~]]
|
|
$(kas [%done p.kas u.q.kas])
|
|
|
|
This is a sort of a `++need` for silks. If there is no data in the unit
|
|
cage, we produce an error. Else, we simply produce the data in the cage.
|
|
|
|
Lifcycle of a `%mute`
|
|
---------------------
|
|
|
|
%mute (kale cof p.kas q.kas)
|
|
|
|
This mutates a silk by putting the values of other silks at particular
|
|
axes. This is useful in, for example, replacing the sample of the door
|
|
in a mark definition.
|
|
|
|
++ kale :: mutate
|
|
|= [cof=cafe kas=silk muy=(list (pair wing silk))]
|
|
^- (bolt cage)
|
|
%+ cope
|
|
|- ^- (bolt (list (pair wing vase)))
|
|
?~ muy (fine cof ~)
|
|
%+ cope (make cof q.i.muy)
|
|
|= [cof=cafe cay=cage]
|
|
%+ cope ^$(muy t.muy)
|
|
|= [cof=cafe rex=(list (pair wing vase))]
|
|
(fine cof [[p.i.muy q.cay] rex])
|
|
|= [cof=cafe yom=(list (pair wing vase))]
|
|
%+ cope (make cof kas)
|
|
|= [cof=cafe cay=cage]
|
|
%+ cope (keel cof q.cay yom)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof p.cay vax)
|
|
|
|
First, we process each of the silks by calling `++make` on them. We pass
|
|
the resultant vase and list of pairs of wings and silks to `++keel` to
|
|
do the actual mutation. We assume the mutation doesn't change the mark
|
|
of the main silk, so we mark the produced vase with the original mark.
|
|
|
|
++ keel :: apply mutations
|
|
|= [cof=cafe suh=vase yom=(list (pair wing vase))]
|
|
^- (bolt vase)
|
|
%^ maim cof
|
|
%+ slop suh
|
|
|- ^- vase
|
|
?~ yom [[%atom %n] ~]
|
|
(slop q.i.yom $(yom t.yom))
|
|
^- twig
|
|
:+ %cncb [%& 2]~
|
|
=+ axe=3
|
|
|- ^- (list (pair wing twig))
|
|
?~ yom ~
|
|
:- [p.i.yom [%$ (peg axe 2)]]
|
|
$(yom t.yom, axe (peg axe 3))
|
|
|
|
We first put the vases together in one big tuple starting with the
|
|
subject and going through the mutations. We slap against this tuple a
|
|
`%_` twig we directly construct. Since a `%_` twig takes a list of pairs
|
|
of wings and twigs, we simply have to generate twigs referring to the
|
|
correct axes in the subject. This is very easy since we just recur on
|
|
axis 3 of whatever axis we were already at.
|
|
|
|
Note the use of `%_` instead of `%=` enforces that our mutations don't
|
|
change the type of the subject, which justifies our use of the original
|
|
mark.
|
|
|
|
Lifecycle of a `%pact`
|
|
----------------------
|
|
|
|
%pact
|
|
%+ cool |.(leaf/"ford: pact {<`@p`(mug p.kas)>} {<`@p`(mug q.kas)>}")
|
|
(pact cof p.kas q.kas)
|
|
|
|
We push debug information onto the trace and go right to `++pact`.
|
|
|
|
++ pact :: patch
|
|
|= [cof=cafe kas=silk kos=silk]
|
|
^- (bolt cage)
|
|
%. [cof kas kos]
|
|
;~ cope
|
|
;~ coax
|
|
|=([cof=cafe p=silk q=silk] (make cof p))
|
|
|=([cof=cafe p=silk q=silk] (make cof q))
|
|
==
|
|
|= [cof=cafe cay=cage coy=cage]
|
|
|
|
First, we process the two given silks to get our arguments.
|
|
|
|
%+ cope (fang cof p.cay [our %main %da now])
|
|
|= [cof=cafe pro=vase]
|
|
|
|
We pull in the relevant mark's definition.
|
|
|
|
?. (slab %grad p.pro)
|
|
(flaw cof leaf/"no ++grad" ~)
|
|
=+ gar=(slap pro [%cnzy %grad])
|
|
?. (slab %form p.gar)
|
|
?. (slab %sted p.gar)
|
|
(flaw cof leaf/"no ++form:grad nor ++sted:grad" ~)
|
|
=+ for=((soft ,@tas) q:(slap gar [%cnzy %sted]))
|
|
?~ for
|
|
(flaw cof leaf/"bad ++sted:grad" ~)
|
|
(make cof %cast p.cay %pact [%cast u.for kas] kos)
|
|
|
|
If there's no `++grad`, we complain. If there's no `++form:grad`, then
|
|
we look for a `++sted:grad`. If we can't find either, or if
|
|
`++sted:grad` isn't a term, then we complain. If `++sted:grad` exists
|
|
and is a term, then it represents the mark we should use as a proxy to
|
|
get our diff. So, we cast the first argument to the new mark, then try
|
|
to patch. Afterward, we cast the result back to the original mark.
|
|
|
|
=+ for=((soft ,@tas) q:(slap gar [%cnzy %form]))
|
|
?~ for
|
|
(flaw cof leaf/"bad ++form:grad" ~)
|
|
?. =(u.for p.coy)
|
|
%+ flaw cof :_ ~
|
|
=< leaf/"pact on data with wrong form: {-} {+<} {+>}"
|
|
[(trip p.cay) (trip u.for) (trip p.coy)]
|
|
|
|
If `++form:grad` isn't a term, or else our second argument isn't of that
|
|
mark, we complain.
|
|
|
|
?. (slab %pact p.gar)
|
|
(flaw cof leaf/"no ++pact:grad" ~)
|
|
|
|
If we don't have a `++pact:grad`, we complain.
|
|
|
|
%+ cope (keel cof pro [[%& 6]~ q.cay]~)
|
|
|= [cof=cafe pox=vase]
|
|
|
|
We put the first cage's data into the sample of the given mark's
|
|
definition.
|
|
|
|
%+ cope
|
|
%^ maul cof
|
|
(slap (slap pox [%cnzy %grad]) [%cnzy %pact])
|
|
q.coy
|
|
|= [cof=cafe pat=vase]
|
|
|
|
We run `++pact:grad` with a sample of the second cage's data, which is
|
|
the diff.
|
|
|
|
(fine cof p.cay pat)
|
|
==
|
|
|
|
We tag the result with the mark of our first argument.
|
|
|
|
Lifecycle of a `%plan`
|
|
----------------------
|
|
|
|
%plan
|
|
%+ cope (abut:(meow p.kas q.kas) cof r.kas)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof %noun vax)
|
|
|
|
This is a direct request to compile a hood at a given beam with a heel
|
|
of the given path. We comply by calling `++abut` with the given
|
|
arguments and producing the vase with a mark of `%noun`.
|
|
|
|
Lifecycle of a `%reef`
|
|
----------------------
|
|
|
|
%reef (fine cof %noun pit)
|
|
|
|
This is one of the simplest silks. We simply produce our context, which
|
|
is zuse compiled against hoon. The mark is a `%noun`.
|
|
|
|
Lifcycle of a `%ride`
|
|
---------------------
|
|
|
|
%ride
|
|
%+ cool |.(leaf/"ford: ride {<`@p`(mug kas)>}")
|
|
%+ cope $(kas q.kas)
|
|
|= [cof=cafe cay=cage]
|
|
%+ cope (maim cof q.cay p.kas)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof %noun vax)
|
|
|
|
This slaps evaluates the given silk, then it slaps the result against
|
|
the given twig. Since we don't know what of what mark (if any) is the
|
|
result, we give it a mark of `%noun`.
|
|
|
|
Lifecycle of a `%vale`
|
|
----------------------
|
|
|
|
%vale
|
|
%+ cool |.(leaf/"ford: vale {<p.kas>} {<q.kas>} {<`@p`(mug r.kas)>}")
|
|
%+ cope (lave cof p.kas q.kas r.kas)
|
|
|= [cof=cafe vax=vase]
|
|
(fine cof `cage`[p.kas vax])
|
|
|
|
This checks whether given data is of the given mark. If we don't have
|
|
the definition of the mark, we check the given ship for it.
|
|
|
|
We call `++lave` to perform the check, producing a vase. We produce this
|
|
vase tagged with the given mark.
|
|
|
|
++ lave :: validate
|
|
|= [cof=cafe for=mark his=ship som=*]
|
|
^- (bolt vase)
|
|
((lake for [our %main [%da now]]) cof [%noun som])
|
|
|
|
This is a thinly-veiled wrapper over `++lake`. Note that, contrary to
|
|
documented opinion, we do not in fact check the other ship's definition
|
|
of a mark. This is likely a bug.
|
|
|
|
At any rate, `++lake` coerces a noun into the correct type for a mark.
|
|
|
|
++ lake :: check/coerce
|
|
|= [for=mark bek=beak]
|
|
|= [cof=cafe sam=vase]
|
|
^- (bolt vase)
|
|
%+ cool |.(leaf/"ford: check {<[for bek `@p`(mug q.sam)]>}")
|
|
?: ?=(?(%gate %core %door %hoon %hook) for)
|
|
:: ~& [%lake-easy for bek]
|
|
(fine cof sam)
|
|
%+ cope (fang cof for bek)
|
|
|= [cof=cafe tux=vase]
|
|
=+ bob=(slot 6 tux)
|
|
?: (~(nest ut p.bob) | p.sam)
|
|
(fine cof sam)
|
|
?. (slab %grab p.tux)
|
|
(flaw cof [%leaf "ford: no grab: {<[for bek]>}"]~)
|
|
=+ gab=(slap tux [%cnzy %grab])
|
|
?. (slab %noun p.gab)
|
|
(flaw cof [%leaf "ford: no noun: {<[for bek]>}"]~)
|
|
%+ cope (maul cof (slap gab [%cnzy %noun]) [%noun q.sam])
|
|
|= [cof=cafe pro=vase]
|
|
?: =(+<.q.pro q.sam)
|
|
(fine cof (slot 6 pro))
|
|
(flaw cof [%leaf "ford: invalid content: {<[for bek]>}"]~)
|
|
|
|
This is going to coerce the sample into the correct type for the mark.
|
|
First, we push a line onto the stack trace saying that we're checking
|
|
the type. If the requested mark is a gate, core, door, hoon, or hook,
|
|
then we don't do any more type information than just saying it's a noun,
|
|
so we're done.
|
|
|
|
Otherwise, we get the mark definition from our `/=main=/mar` directory
|
|
with `++fang`, which we'll describe below.
|
|
|
|
We check to see if our sample type nests within the type of the sample
|
|
to the door. If so, then we're already of the correct type, so we're
|
|
done.
|
|
|
|
Otherwise, we check to see if there's a `++grab` in the door, and a
|
|
`++noun` in the `++grab`. If not, there's no way we can translate to
|
|
this mark, so we fail.
|
|
|
|
If we have everything we need, we slam our sample (typed as a noun)
|
|
against the `++noun` in `++grab`. If the sample of the door is the same
|
|
as our sample, then the check succeeded, so we produce the well-typed
|
|
sample of the door. Otherwise, we fail.
|
|
|
|
++ fang :: protocol door
|
|
|= [cof=cafe for=mark bek=beak]
|
|
^- (bolt vase)
|
|
=+ pax=/door/[for]/mar
|
|
=+ ^= bem ^- beam
|
|
:_ pax
|
|
?: =(p.bek our) bek
|
|
=+ oak=[our %main %da now]
|
|
?. =(~ (ska %cy (tope [oak pax]))) oak
|
|
bek
|
|
(cope (fade cof %hook bem) abut:(meow bem ~))
|
|
|
|
A mark's definition is generally in
|
|
`/=main=/mar/[mark-name]/door/hook'. If we don't find it there, we look in`/[given-beak]/mar/[mark-name]/door/hook'.
|
|
We parse the mark definition with `++fade` and assemble it with
|
|
`++abut:meow`. `++fade` is defined under the `%bake` silk.
|