Compiler Dispatcher — Specification
Metanotation
Two metalanguages are used in this section:
- plain English and
- the language of MANOOL expressions (whereby involving a metacircular specification).
All MANOOL expressions in the metacircular specification are to be considered in the binding environment of the MANOOL standard library.
In Plain English
Run-time code, which is the output of the semantic analysis and code generation translation phase, is represented by a compiled entity, which internally (in its turn) may contain other compiled entities. Incidentally, compiled entities are also what identifiers are bound to in a binding environment.
A kind of object-oriented approach is used in this specification — compiled entities are considered to be active agents (represented by a MANOOL value/object for the purposes of the metacircular description) capable of providing services whenever the abstract machine asks them to.
The abstract machine has a (very) simple and compact semantic analysis and code generation core dispatcher. To compile an expression, the dispatcher first determines what kind of AST represents it:
-
If it is a symbol (explicitly) bound to a compiled entity in the active binding environment, that compiled entity becomes the result of compilation.
-
Otherwise, if it is an integer, a string, or a symbol, and the symbol starts with a character other than an ASCII lowercase letter (
a
thruz
), the abstract machine constructs a compiled entity that represents a literal value. -
Otherwise, if it is a compound expression, the dispatcher compiles (recursively) the head subexpression and then asks the resulting compiled entity to compile the whole expression.
-
Otherwise, if it is a special MANOOL value that encapsulates a compiled entity, which is produced by a
#
-expression (and for which the predicateIsCode
returnsTrue
), then the encapsulated compiled entity becomes the result of compilation. -
As a fallback, the dispatcher reports an error as appropriate.
Note that checking the presence and placement of keywords (such as then
, else
, do
, etc.) is performed, if needed, by compiled entities rather than
directly by the dispatcher. Also, a compiled entity, when asked to compile an expression, may in turn call the dispatcher for subexpressions1 and possibly
specify a different active binding environment to compile in.
Metacircular Specification
For the purposes of illustration, let's assume that a compiled entity recognizes the following polymorphic operations (beyond the standard ones):
-
IsRvalue
— tell whether the compiled entity corresponds to an r-value expression -
IsLvalue
— tell whether the compiled entity corresponds to an l-value expression (which shall imply a positive answer to the above question as well) -
Compile
— given an active binding environment represented by a mappingSymTab
, compile the specified compound expression (whose head subexpression corresponds to the compiled entity) to produce as a result another compiled entity -
Execute
— given an evaluation contextCtx
, evaluate the expression the compiled entity represents and produce a resulting value (applicable only in case of r-value expressions) -
ExecIn
— given an evaluation contextCtx
and a valueVal
, store the value into the location the compiled entity represents (applicable only in case of l-value expressions) -
ExecOut
— given an evaluation contextCtx
, move out the current value from the location the compiled entity represents (applicable only in case of l-value expressions) to produce as a result the value moved out
The core dispatcher algorithm described above may be specified more formally using the following metacircular description (in MANOOL):2
{ let rec
{ Compile =
{ proc { Form; SymTab } as
: if IsSym[Form] & SymTab.Exists[Form] then SymTab[Form] else -- bound identifier
: if IsInt[Form] | IsStr[Form] | IsSym[Form] &
{ do True after
: if (Str[Form] <> "") & (Str[Form][0] >= "a"[0]$) & (Str[Form][0] <= "z"[0]$) then
: signal CompileError with "unbound keyword (nested in this context)"
}
then -- literal value
{ let { _Form } in
: object { _Form = Form } with
IsRvalue = {proc {_} as True}
IsLvalue = {proc {_} as False}
Compile' = CompileApply
Execute = {proc {Self; _} as Self[_Form]@}
}
else
: if IsList[Form] & (Size[Form] <> 0) then Compile[Form[0]; SymTab].(Compile')[Form; SymTab] else -- compound expression
: if IsCode[Form] then Form[_Entity]@ else -- a result of e# (used for metaprogramming)
: signal CompileError with "invalid form"
}
}
in
: export Compile
}
As a matter of illustration, a compiled entity bound to the if
keyword could be constructed by evaluating the expression
{ object {} with
-- Classification
IsRvalue = {proc {_} as False}
IsLvalue = {proc {_} as False}
-- Compilation
Compile' =
{ proc { _; Form; SymTab } as
: if (Size[Form] >= 6) & (Form[2] == then') & (Form[4] = else') then
{ let { _Cond; _Body1; _Body2 } in
: object
{ _Cond = CompileRvalue[Form[1]; SymTab]
_Body1 = CompileRvalue[Form[3]; SymTab]
_Body2 = CompileRvalues[Form.Elems[Range[5; Size[Form]]]; SymTab]
}
with
-- Classification
IsRvalue = {proc {_} as True}
IsLvalue = {proc {Self} as Self[_Body1]@.IsLvalue[] & Self[_Body2]@.IsLvalue[]}
-- Execution
Execute = {proc {Self; Ctx} as Execute[Self[{if Execute[Self[_Cond]@; Ctx] then _Body1 else _Body2}]@; Ctx]}
ExecIn = {proc {Self; Val; Ctx} as ExecIn[Self[{if Execute[Self[_Cond]@; Ctx] then _Body1 else _Body2}]@; Val; Ctx]}
ExecOut = {proc {Self; Ctx} as ExecOut[Self[{if Execute[Self[_Cond]@; Ctx] then _Body1 else _Body2}]@; Ctx]}
-- Compilation
Compile' = CompileApply
}
else
: if (Size[Form] >= 4) & (Form[2] == then') then
{ let { _Cond; _Body } in
: object
{ _Cond = CompileRvalue[Form[1]; SymTab]
_Body = CompileRvalues[Form.Elems[Range[3; Size[Form]]]; SymTab]
}
with
-- Classification
IsRvalue = {proc {_} as True}
IsLvalue = {proc {_} as False}
-- Execution
Execute = {proc {Self; Ctx} as: if Execute[Self[_Cond]@; Ctx] then Execute[Self[_Body]@; Ctx]}
-- Compilation
Compile' = CompileApply
}
else
: signal CompileError with "invalid form"
}
}