Context
What is the Context?
The context can be viewed as the working image of the Juvix Language. In particular it is the storage/information system of the language. It is also responsible for having the semantics of symbol resolution Due to this, we can view the context as a fancy nested hashtable.
What information is stored in the Context?
The context stores a plethora of information.
Currently the Context Contains
Definitions
These hold the Standard desugared Juvix definitions.
Types
These are the data type declarations in the Juvix language.
Every definition also has a type attached as well that one can query.
Constructors
These are stored like Definitions, but for type consturctors.
Records/Modules
These are recursive name-spaces.
Each record/module holds two name-spaces inside of them, a public and private one.
Precedence
This is associated with a definition, so we know how to desugar precedence.
Reverse Module use lookup
We get to know what modules import what module, and if they are implicit or explicit.
Current Module/Record
This is the current module in scope.
In the long run this information will be added to, with each declared item in the image having a general hashtable that any information can be stored on. This will include documentation and the original source code.
How does Symbol resolution work?
Symbol Resolution is handled entirely by the context. When we ask the
context what Foo.Bar.zed
resolves to, we do a few things.
First we check if Foo
is in the current name-space. Specifically if
Foo
is both in the private name-space and public one, then the
private one wins. After this point, we have no means of accessing
private name-spaces, so we just access the public Bar and zed inside
the Foo
module we resolved.
If Foo
is not in the locals, then we check the top of the context
for Foo
. If it's not there, then we are unable to resolve it.
The only other special part of a known symbol is the resolution of
names like TopLevel
which bypasses any local module checks, and
instantly resolves from the top. so Toplevel.Foo.Bar.zed
will always
look in the toplevel name-space.
Special Rules for dealing with unresolved symbols
Sometimes we wish to quantify over symbols which we may add or
create. For our previous example, what if Foo.Bar.zed
is undefined?
Operations which operate over hypothetical symbols have a way to know
if the symbol is defined or not.
The resolution rules are fairly simple, but we will investigate them
with three case studies where the symbols may be undefined. zed
,
Foo.zed
, TopLevel.Foo.zed
zed
Since
zed
is not in scope, we only really have two choices. We either meanToplevel.zed
orCurrentNameSpace.zed
. Since we find that we typically meanzed
in the current module, and not some hypothetical one at a top level, we try to do the latter. So the operation deals with an undefinedCurrentNameSpace.zed
Foo.zed
Since This case can have a condition where
Foo
could also be resolved, we break this into another case analysis.Foo
is resolvedIn this case, we act much like the 1) case, where we explicitly work in an unresolved
CurrentNameSpace.Foo.zed
. or aToplevel.Foo.zed
depending on which one is in scope (see the scoping section above!).
Foo
is not resolvedIn this case we actually do a different resolution than the
zed
case. We assume we are talking a toplevel version, namelyToplevel.Foo.zed
Toplevel.Foo.zed
This one is rather easy as well, as it falls back to case 2 subsection 2.
Overall some of these rules are arbitrary, namely case 2 subsection 2 may need to conform to be more like case 1, however this requires testing and playing with the system more to determine.
Both this section and the above are implemented in the modify speace impure section.
What are the Functions of the Context?
The context hosts a plethora of useful functions that make traversing it easy. Most of these more useful functions are built upon the more primitive operations.
Many operations that create name-spaces are in IO, to hold the recrusive maps we use to track information.
Primitive Operations
Empty
empty :: NameSymbol.T -> IO (T term ty sumRep)
empty
creates a new context in the name-space you call it with (thus
empty "Foo.Bar"
makes a context with the nested module Bar in Foo).
The current module is then set to this name.
Lookup
lookup ::
NameSymbol.T -> T term ty sumRep -> Maybe (From (Definition term ty sumRep))
(!?) ::
T term ty sumRep -> NameSymbol.T -> Maybe (From (Definition term ty sumRep))
looup
looks up the given name-symbol according to the rules in the
Symbol resolution section. !?
is just flipped lookup.
Add
add ::
NameSpace.From Symbol ->
Definition term ty sumRep ->
T term ty sumRep ->
T term ty sumRep
add name def context
adds name
with the definition def
to the
current name-space of an existing context. The type NameSpace.From
can either be Pub
or Priv
currently, stating how we want to add
the definition to the current context.
As can be observed, we are adding a Symbol
and not a NameSymbol
,
and so this primitive can't add name
to a separate module. This
should be changed in time.
Remove
remove ::
NameSpace.From Symbol ->
T term ty sumRep ->
T term ty sumRep
remove name context
removes the name
from the public or private
name-space of the current name-space.
InNameSpace
inNameSpace :: NameSymbol.T -> T term ty sumRep -> Maybe (T term ty sumRep)
inNameSpace name context
is the primitive way in which we change the
current name-space. like lookup, we use the standard symbol resolution
rule to determine which module we resolve to. If the module is not
found, then we simply return Nothing
. Also if the definition we land
on is not a record/module, then likewise we also return Nothing
. If
all is successful, then we end up with a new context with a new
current name-space.
ModifySpaceImp ∧ type Stage ∧ type Return
data Stage b
= -- | 'Final' signifies the last symbol that we
-- pursue in updating a structure
Final b
| -- | 'Continue' signifies that there are parts of
-- the namespace that we can still continue down
Continue b
data Return term ty sumRep
= -- | 'GoOn' signifies that we should continue
-- going down records
GoOn (Record term ty sumRep)
| -- | 'Abort' signifies that we should cancel
-- the changes on the map and
Abort
| -- | 'UpdateNow' signifies that we should
-- update the context with the current value
UpdateNow (Definition term ty sumRep)
| -- | 'RemoveNow' signifies that we show remove
-- the definition at this level
RemoveNow
modifySpaceImp ::
Monad m =>
( Stage (Maybe (Definition term ty sumRep)) ->
m (Return term ty sumRep)
) ->
NameSymbol.T ->
T term ty sumRep ->
m (Maybe (T term ty sumRep))
modifySpaceImp f sym context
is a rather important operation. Some
forms like Empty even rely on it!
modifySpaceImp
is responsible for implementing the behavior for
known symbols exactly how Symbol Resolution
lays it out, and for unknown symbols how Rules for Unresolved Symbols lays it out.
The Maybe
it takes indicates whether the symbol is resolved or
not.
We decided to include the Return
and Stage
type here, as they both
talk about something integral to the design of this function. Namely
the Return
talks about the range of operations we care about. This
limits the kinds of operations this modifyNameSpaceImp
function can
do. The Stage
on the other hand denotes to the operation function
f
whether we are at the final symbol in the resolution or are we
still going down to nested structure to find the proper symbol. Many
operations will do something special at the very end, and so we often
care about what stage we are at.
Finally, our function returns some monad m
, this is due to just
being general to accept any kind of function, as the name suggests
there is a non Impure
version of this, however this just runs this
more general one in the Identity monad.
Derived Operations
These operations aren't derived from the primitive operations, however are quite important for operation of the context. Some even have special rules one must pay attention to.
SwitchNameSpace
switchNameSpace ::
NameSymbol.T -> T term ty sumRep -> IO (Either PathError (T term ty sumRep))
switchNamespace name context
is an important operation. This creates
and switches to the name-space even if it doesn't exist! The rules for
creation are exactly that ruled in the Symbol Resolution section.
The creation of new name-spaces are IO, and so the operation is in IO as well.