---
Summary: Notes on where context should and should not be
Updated: 17 September 2016
git-hash: 7319521e44d149c565f6bed61c28b3a64421da8e
---
NOTE: This document is in flux and not yet canonical. It's very much a WIPNOTE
of the purest form.

Context
=======
A context is a type oriented equivalent for a python namespace.

That is a python namespace maps names to values. A context maps names to value types.

It's yet to be decided if it is name to a value-type or value-type-set.
It's likely to be the latter with the constraint of either: the set being
a singleton, OR, the set being constructued solely of types that are
compatible. (That is subtyped to be the same. That's unclear at present
whether a good or bad idea - especially given all types are subclasses/
subtypes of class "object")

It's likely any enforcement here wants to actually be done at the analysis
stage.

Summary
-------
Analysed the current approach - what's in each node now, and what info
they need to determine their state - directly or child nodes. Child nodes
*could* be decorated by an external pass. On some levels, an external pass
handling this sounds like a good idea. It's certainly a good idea that
the external pass CAN do this.

However, logically a context is inherited by child nodes, and this sounds
like something that parent nodes can probably handle better than an
external agency.

As a result, the decision is this:

 - When a node is created, and given a context, that context will
   be inherited by child nodes.

 - Child nodes can refuse to inherit the context if appropriate
   (a function definition may want to not have its context overridden,
   but that function definition may well want to have access to the
   parent context) 

 - As a result, each node will have a context.

 - Each node will have a function called "set_environment_context"

 - That set_environment_context can either override the node's context
   or be captured by the pynode

 - If the pynode's local context is NOT overridden by the parent's
   environment context then the pynode's context will have it's *parent*
   list updated to include the parent node (and maybe the parent node's
   context)

Review point - (mid) September 2016
-----------------------------------
Note: September 2016: The above reads really quite unclear. It also actually
looks a little wrong in practice. Leaving as is for the moment while we work
through this.

Functions need access to a parent context.
Functions also need access to a local context.
Expressions need access to the context they sit inside.

As a result, nodes are either context owners, and/or context users.

For a context user, the context forms a runtime environment. 

For a context owner, the runtime environment is the local context and the runtime environment. The fact they own it though is almost irrelevant to them. It's more a matter that they created it and want to use it.

In particular though, the parentage is that the context sits within a context.

We just want to ask the context "What is the value of"
We also want to be able to flag to the context "this value is local",
"this value is non-local", "this value is global"

Nodes themselves should not actually care *at all* about where they are getting values from.

Currently we do support contexts knowing about parent contexts. That is likely to be our starting point here.

The issue we have though is that contexts currently "store" expressions, which seems a little odd, but is clearly related to current code structure/analysis, which is known to be too simplistic.

A context should really be a relationship between names and types, not names and expressions.

So, perhpas we need to review what a context actually stores.

    Context -> Map between names and context values
    Context values -> None
                   -> Types
                   -> Expression to derive types from
                   -> Expressions to derive types from
                   -> Currently uses that last one.

Storing of expressions happens currently within model/pynodes/values.py

Specifically:

    class PyIdentifier(PyValueLiteral):
        ...
        def add_rvalue(self, expression):
            self.context.store(self.value, expression)
        def analyse(self):
            expression = self.context.lookup(self.value)
            self.ntype = expression.get_type()

add_rvalue is called within model/pynodes/statements.py here:

    class PyAssignment(PyStatement):
        def __init__(self, lvalue, rvalue, assign_type):
            super(PyAssignment,self).__init__()
            self.lvalue = lvalue
            self.rvalue = rvalue
        ...
        def analyse(self):
            print("ANALYSING ASSIGNMENT")
            print("ANALYSE RIGHT")
            self.rvalue.analyse()
            self.lvalue.add_rvalue(self.rvalue)
            self.lvalue.analyse()

    class PyForLoop(PyStatement):
        tag = "for_statement"
        def __init__(self, identifier, expression, block):
            super(PyForLoop,self).__init__()
            self.identifier= identifier
            self.expression = expression

        def analyse(self):
    ...
            self.identifier.add_rvalue(expression) # The result of the expression becomes a repeated rvalue for the identifier, so this sorta makes sense
            self.identifier.analyse()


Let's therefore define a context value:
    - May contain 1 or more expressions
    - May contain a type
    - May actually state "This value is global" - May come from explicit or non-explicit statement
    - May actually state "this value is non-local" - May come from explicit or non-explicit statement
    - May actually state "this value is local"
    - May be empty  (This allows for certain python expressions to work correctly)

This should allow us to do the type analysis we've been wanting to do.

Furthermore, the following is probably going to happen (but not decided):

 - Objects will be defined to have a context
 - Classes will be defined to have a context, and a class's context would
   be an environment context for objects.
   
   How we use that is up for grabs at present but that's the core.

This is largely noted so we can bear this in mind as we work through how
contexts work in practice, rather than theory.


Current Approach
----------------

We only add a context to identifiers.

global_context = Context()
for node in depth_walk(self):
    if node.tag == "identifier":
        node.context = global_context
        print("NODE", node)

I think this approach is wrong. Seemed like a good idea at the time though.

What makes sense?
-----------------

Unclear, so worked through what would make sense for each sort of 
PyNode. ie Each part of the grammar at present. At present we do not
have a concept of global/local/non-local context. Objects are not yet
implemented. We know they're coming though. The following bears this
in mind.

A PyProgram talks about a global_context. Other pynodes like PyBlock
and PyStatements talk about *a* context. This recognises that classes,
objects and functions will come into existance. Clearly their
contexts will include nested contexts.

This can be unified by PyProgram gaining *a* context as well.

Issue/Dicrepancy: Nested functions do not exist in C++.
Potential Mitigation: Nested classes/structs do...

STRUCTURAL PYNODES -- All inherit PyNode

  PyProgram --
    - Has the global context
    - Provides access to the global context to child nodes

  PyBlock --PyNode
    - Is a collection of statements
    - Does not create a new context.
    - Is provided with a context. (sits in a context)
    - Provides that context to child nodes

  PyStatements --PyNode
    - Is a collection of statements
    - Does not create a new context.
    - Is provided with a context. (sits in a context)
    - Provides that context to child nodes

  PyExprList --PyNode
    - Is a collection of expressions
    - Does not create a new context.
    - Is provided with a context. (sits in a context)
    - Provides that context to child nodes

STATEMENT PYNODES -- All inherit PyStatement(PyNode)

  PyAssignment
    - Updates an lvalue
    - Indicates a need to update a context
    - SHOULD have access to a context
    - DOESN'T have access to a context
    - Does not create a new context.

    - In fact, the context that the assignment sits in matters.
      Why?

      If a value is inside a scope or not affects whether you get/set

      in the current context or an enclosing context.

      Also, there is the issue of "global" or "nonlocal" keywordds.
      
      This really suggests that contexts do need to be accessed this low in the tree.

  PyExpressionStatement
    - Values may require access to a context to evaluate
    - Does not create a new context
    - Does not update a context

  PyFunctionCall
    - May require access to a context to evaluate
    - Does not create a new context
    - Does not update a context

  PyForLoop
    - Similar to assignment effectively
    - Has a block that requires a context
    - Has an lvalue that gets updated, implying access to a context
    - Indicates a need to update a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.

  PyWhileStatement
    - Has a block that requires a context
    - Has an expression that requires a context to evaluate
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.

  PyIfStatement
    - Has a block that requires a context
    - Has an expression that requires a context to evaluate
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.


  PyElIfClause
    - Has a block that requires a context
    - Has an expression that requires a context to evaluate
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.

  PyElseClause
    - Has a block that requires a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.

  PyEmptyStatement
    - Shouldn't need anything...
    - Symbolises blank lines.
    - Should be potentially filtered out better.
    - Feels like a kludge
  
  PyPassStatement
    - Shouldn't need anything...

  PyBreakStatement
    - Shouldn't need anything...

  PyContinueStatement
    - Shouldn't need anything...

  PyPrintStatement
    - Has a collection of expressions that require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Actual type of result does NOT depend on context
    
OPERATOR PYNODES -- All inherit PyOperation(PyNode)

  PyOperator(PyOperation):
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES depend on context

  PyBoolOperator(PyOperation):
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES NOT depend on context - Always TRUE/FALSE (Bool)

  PyComparisonOperator(PyOperation):
    NTS:: NOT CONVINCED THESE ARE ACTUALLY CORRECT
  
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result may depend on context (though always eval to true/false)


BOOL OPERATORS -- All inherit PyBoolOperator(PyOperation(PyNode))

  PyAndOperator(PyBoolOperator): (Binary)
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - *Type* of result DOES NOT depend on context - Always TRUE/FALSE (Bool)

  PyOrOperator(PyAndOperator): (Binary)
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - *Type* of result DOES NOT depend on context - Always TRUE/FALSE (Bool)

  PyNotOperator(PyBoolOperator):   (Unary operator)
    - Takes 1 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES NOT depend on context - Always TRUE/FALSE (Bool)

OTHER OPERATORS -- All inherit PyOperator(PyOperation(PyNode))

  PyTimesOperator
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES depend on context

  PyDivideOperator
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES depend on context

  PyPowerOperator
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES depend on context

  PyPlusOperator
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES depend on context

  PyMinusOperator
    - Takes 2 expressions that each may require a context
    - SHOULD have access to a context
    - DOESN'T have access to a context (except indirectly)
    - Does not create a new context.
    - Type of result DOES depend on context

VALUE NODES - All inherit PyNode

class PyValueLiteral(PyNode):
     - Base class -- not all subclasses have need for context
     - In fact most don't need it
     - No need to be enforced here

class PyIdentifier(PyValueLiteral):
     - Represents a name for a value.
     - That value has a type
     - Requires access to a context to extract this
     - Does not necessarily update the context
     - Does not create a context
     - HOWEVER, identifier MAY be used during assignment which could mean
       a context MAY need updating here.

class PyAttributeAccess(PyNode):
     - Represents extraction of a value by name from a value
     - That extracted value has a type
     - Requires access to a context to extract this
     - Does not necessarily update the context
         - But could ?
         - Does this depend on context it's extracted *into* ?
         - Yes, could actually.
     - Does not create a context
          - Is that true?
          - not in the general case it's true.
          - Could cause update of the context *within* the value
     - Attribute access MAY be used during assignment

     - Attributes are essentially accessors to specific contexts that sit within objects.
     - Some of those attributes may be functions.
     - Some will be plain values.
     - But the object IS a context
        - But unlike Pascal NOT one that can be opened (via with) - but one that remains closed.

class PyAttribute(PyNode):
     - Represents a name for a value.
     - That value has a type
     - Requires access to a context to extract this - BUT that is handled by attribute access
     - So this does NOT update or any way impact into a context directly. Rather it is
     - used by something else to do that.
     - So at least in theory does not need access to a context.


   --- PyAttribute is used IN PyAttributeAccess to represent the name to extract or update



class PyString(PyValueLiteral):
     - As a literal has no need for context in anyway

class PyCharacter(PyValueLiteral):
     - As a literal has no need for context in anyway

class PyBoolean(PyValueLiteral):
     - As a literal has no need for context in anyway

class PyNumber(PyValueLiteral):
     - As a literal has no need for context in anyway
     - true for all subclasses

class PyFloat(PyNumber):
     - As a literal has no need for context in anyway

class PyInteger(PyNumber):
     - As a literal has no need for context in anyway

class PySignedLong(PyNumber):
     - As a literal has no need for context in anyway

class PyUnSignedLong(PyNumber):
     - As a literal has no need for context in anyway

class PyHex(PyNumber):
     - As a literal has no need for context in anyway

class PyOctal(PyNumber):
     - As a literal has no need for context in anyway

class PyBinary(PyNumber):
     - As a literal has no need for context in anyway
