1.ย Language definition
The Laurel language definition consists of its types, its grammar and its semantics. Currently the semantics is split into a static part, called the resolver, and a yet-to-be-built operational part.
The parts of the language definition map onto the implementation files as follows:
-
Type โ
LaurelAST.leandefines the Laurel AST, including the program structure (StmtExpr, declarations, procedures) and the type language (HighType).LaurelTypes.leancomputes theHighTypeof an expression from these annotations, andTypeHierarchy.leancaptures the subtyping relation between user-defined types. -
Grammar โ
Grammar/LaurelGrammar.stis the DDM dialect that defines Laurel's concrete syntax; it is loaded into Lean byGrammar/LaurelGrammar.lean.Grammar/ConcreteToAbstractTreeTranslator.leanturns the parsed concrete tree into theLaurelASTtype, andGrammar/AbstractToConcreteTreeTranslator.leangoes the other way to render an AST back to concrete syntax. -
Static semantics (resolver) โ
Resolution.leanresolves references and type checks the program, producing diagnostics and aSemanticModel(defined inSemanticModel.lean) that links references to their definitions. -
Operational semantics โ Laurel does not yet have a standalone interpreter; its runtime meaning is given operationally by the compilation to Core described below. The pass files under
StrataLaurel/Implementation/and the pipeline inLaurelCompilationPipeline.leantherefore constitute the operational semantics, delegating to Core's own execution and verification semantics.
Laurel program type definition The Laurel type definition allows many more programs than are required for the language as it is documented for the user. The Laurel AST has some constructs that are only used by the compilation passes, but not by the source languages, to enable gradual compilation to Core. Because of these extra constructs we call the Laurel AST wide.
If two Laurel language constructs share semantic properties, we try to capture that sharing in the
AST by having a shared constructor. For example,
instead of having a separate constructor for StmtExpr.Forall and for StmtExpr.Exists, there is a
single StmtExpr.Quantifier with a boolean field to determine its type. A more complicated example:
calls to statically defined user procedures, to datatype constructors, and to datatype destructors
all go through the same StmtExpr.StaticCall constructor; resolution distinguishes them by the
resolved kind of the callee rather than by AST constructor. A further consolidation is in progress
(WIP): calls to primitive operators and to user-defined instance procedures are planned to go
through this same call constructor as well.
All information in the Laurel AST is strongly typed. There are no fields that can hold unstructured data, which could be used by extensions to Strata. Instead, Strata extensions should attach data to AST nodes by referencing them through source locations.
Resolution
The static semantics of Laurel are defined by Resolution.lean. This is where Laurel references are
resolved and where type checking is done. Calling resolve will produce diagnostics and a
SemanticModel that can be used to navigate between definitions and references.
Identifiers that occur in a Laurel program carry an optional uniqueId: int. During resolution,
every identifier that defines a new symbol is given a unique identifier, and every identifier that
refers to a definition is given the unique identifier of the definition it references. The
SemanticModel uses these unique identifiers to provide navigation features.
Right now, Laurel reserves identifier names that start with $ for use in its compilation passes.
In the future we may improve the passes so they never generate names that collide with user-provided
names.