Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Loading Declarations

The loading rules are defined in 7.1-load-declaration.watsup. The central relation is Decl_load, which processes a single declaration and returns an updated loadContext.

relation Decl_load:
  loadContext |- declaration -| loadContext
  hint(input %0 %1)

Most declaration kinds either pass through unchanged or build a callableDef and register it. The interesting cases also retrieve elaborated parameter types from CALLABLE_TYPE.

Passthrough Rules

Type declarations, extern declarations, and match-kind declarations do not contribute anything to the load context. Each was fully handled during type checking. Their loading rules are one-liners that pass the context through unchanged:

rule Decl_load/typeDeclaration:
  LC |- typeDeclaration -| LC

rule Decl_load/externDeclaration:
  LC |- externDeclaration -| LC

rule Decl_load/matchKindDeclaration:
  LC |- MATCH_KIND `{ nameList `} -| LC

Action, Parser, and Control Declarations

All three rules follow the same pattern: extract the callable’s name from the source declaration, look up the elaborated parameterIR* that type checking stored in CALLABLE_TYPE, pair it with the body to form the IR, and register the result.

Here is the action rule:

rule Decl_load/actionDeclaration:
  LC_0 |- actionDeclaration -| LC_1
  -- if ACTION name_action `( _ `) blockStatement = actionDeclaration
  -- if callableId = $id(name_action)
  -- if ACTION parameterIR* = $find_callableTypeDef_l(LC_0, callableId)
  -- if actionDeclarationIR
      = ACTION callableId `( parameterIR* `) blockStatement
  -- if LC_1 = $add_callableDef_l(LC_0, callableId, actionDeclarationIR)

The parameter list in the source declaration is matched with _, discarding the raw surface parameters. The elaborated parameterIR* is fetched from CALLABLE_TYPE instead. This is why CALLABLE_TYPE exists in the load context: it gives loading rules read access to what type checking already computed, without re-running elaboration.

The parser and control rules are identical in structure, substituting PARSER and CONTROL for ACTION and including the body fields specific to each:

rule Decl_load/parserDeclaration:
  LC_0 |- parserDeclaration -| LC_1
  -- if PARSER name
    `( parameterList `)
    `{ parserLocalDeclarationList parserStateList `} = parserDeclaration
  -- if callableId = $id(name)
  -- if PARSER parameterIR* = $find_callableTypeDef_l(LC_0, callableId)
  -- if parserDeclarationIR
      = PARSER callableId
          `( parameterIR* `)
          `{ parserLocalDeclarationList parserStateList `}
  -- if LC_1 = $add_callableDef_l(LC_0, callableId, parserDeclarationIR)

rule Decl_load/controlDeclaration:
  LC_0 |- controlDeclaration -| LC_1
  -- if CONTROL name
    `( parameterList `)
    `{ controlLocalDeclarationList APPLY controlBody `} = controlDeclaration
  -- if callableId = $id(name)
  -- if CONTROL parameterIR* = $find_callableTypeDef_l(LC_0, callableId)
  -- if controlDeclarationIR
      = CONTROL callableId
          `( parameterIR* `)
          `{ controlLocalDeclarationList APPLY controlBody `}
  -- if LC_1 = $add_callableDef_l(LC_0, callableId, controlDeclarationIR)

Instantiation

The instantiation rule is different from the others. Its job is not to build a callableDef but to record which parser and control serve as the NanoSwitch entry point.

rule Decl_load/instantiation:
  LC_0 |- (_TID typeId_target) `( argumentList `) name ';' -| LC_1
  -- if argument* = $flatten_argumentList(argumentList)
  -- if (_TID callableId_parser) `( _ `) = argument*[0]
  -- if parserDeclarationIR
      = $find_callableDef_l(LC_0, callableId_parser)
  -- if (_TID callableId_control) `( _ `) = argument*[1]
  -- if controlDeclarationIR
      = $find_callableDef_l(LC_0, callableId_control)
  -- if LC_1
      = LC_0[ .PARSER = parserDeclarationIR ][ .CONTROL = controlDeclarationIR ]

The NanoSwitch(Parser(), Filter()) main; instantiation declaration passes the parser and control as its first two arguments. The rule extracts their names from argument*[0] and argument*[1], looks up each callableDef from the CALLABLE map, and writes the results into LC.PARSER and LC.CONTROL. The parser and control declarations must already be in CALLABLE when this rule fires, so declaration order matters.

Sequencing and Entry Point

Decls_load sequences declarations using the same nil/cons threading pattern seen throughout the spec:

rule Decls_load/nil:
  LC_0 |- eps -| LC_0

rule Decls_load/cons:
  LC_0 |- declaration_h :: declaration_t* -| LC_2
  -- Decl_load: LC_0 |- declaration_h -| LC_1
  -- Decls_load: LC_1 |- declaration_t* -| LC_2

Program_load is the top-level entry point. It initializes a fresh loadContext from the typingContext produced by type checking, then threads all declarations through Decls_load:

relation Program_load:
  typingContext |- program -| loadContext
  hint(input %0 %1)

rule Program_load:
  TC |- program -| LC'
  -- if declaration* = $flatten_program(program)
  -- if LC = $make_loadContext(TC)
  -- Decls_load: LC |- declaration* -| LC'

The resulting LC' is passed to $make_evalContext, which combines it with TC to build the evalContext used throughout execution.